What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
The right Puppeteer API depends on what “add a script” means. Use page.addScriptTag() when you want a real <script> element in the current document, page.evaluate() for a one-off function that runs immediately in the page context, and page.evaluateOnNewDocument() when setup must run after document creation but before the site’s own scripts. For an iframe, use the matching method on its Frame object.
Choose the API before writing code
| Need | Use | When it runs | What it creates |
|---|---|---|---|
| Insert a local, inline, or remote script | page.addScriptTag() |
When you call it in the current document | A <script> element; resolves to its element handle |
| Run a calculation or DOM operation once | page.evaluate() |
Immediately in the selected page context | No script element |
| Install hooks before application code | page.evaluateOnNewDocument() |
After a new document is created, before that document’s scripts | A registered new-document script |
| Target an iframe | The corresponding method on Frame |
Inside that frame’s execution context | Depends on the method used |
The examples below use modern ESM syntax. Align them with the Puppeteer version installed in your project; the cited API material lists addScriptTag around versions 25.10.0, the Page class around 25.12.0, and some execution references under the next documentation path.
Insert a script element with page.addScriptTag()
page.addScriptTag() is a shortcut for page.mainFrame().addScriptTag(). It accepts path, content, or url, plus options such as id and type. The promise resolves to an element handle for the inserted script, so you can inspect the resulting element.
Inject a local JavaScript file
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', {waitUntil: 'domcontentloaded'});
const scriptElement = await page.addScriptTag({
path: './custom.js',
});
console.log(await scriptElement.evaluate(element => element.src));
} finally {
await browser.close();
}
A relative path is resolved from Node.js process.cwd(), not from the directory containing your source file. If your process starts in a different directory, use an absolute path or construct one deliberately. The injected file executes in the page, not in Node.js.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware match#1 Best Overall
Inject inline JavaScript
await page.addScriptTag({
content: `
window.myFlag = true;
document.documentElement.dataset.automation = 'on';
`,
});
This is useful for a small helper or a self-contained library. The code becomes the contents of a script element in the current main-frame document.
Load a script by URL
await page.addScriptTag({
url: 'https://example.com/custom.js',
});
The browser must be able to retrieve the URL, and the target site’s environment may restrict the load through its policy or network behavior. A valid URL does not guarantee that the remote server is available or that the page will permit the request.
Use an ID or an ES module
await page.addScriptTag({
content: "export const ready = true;",
id: 'automation-module',
type: 'module',
});
Use type: 'module' when the inserted source is an ES module. If you need to avoid duplicate insertion, choose an ID and check for it before adding another tag:
Rank #2
const alreadyAdded = await page.$('#automation-module');
if (!alreadyAdded) {
await page.addScriptTag({
id: 'automation-module',
content: 'window.moduleLoaded = true;',
});
}
Run code once with page.evaluate()
Use evaluate() when the goal is to execute a function, read a value, or change the DOM—not to add a persistent <script> element.
const pageTitle = await page.evaluate(() => document.title);
console.log(pageTitle);
The function is serialized and evaluated in the browser context. It cannot see lexical variables or helper functions that exist only in your Node.js module. Pass values explicitly:
const label = 'Automation test';
await page.evaluate(text => {
document.body.dataset.testLabel = text;
}, label);
Puppeteer awaits a promise returned by the evaluated function and serializes ordinary return values. For an in-page object that must remain available by reference—such as a DOM node—use evaluateHandle() instead of expecting a normal object return to preserve identity.
Wait for asynchronous page code
const result = await page.evaluate(async () => {
const response = await fetch('/api/status');
return response.json();
});
console.log(result);
Always await both the evaluation and the operation that caused the page state you depend on. Otherwise, later assertions can run before your injected code has completed.
Run custom code before the page’s scripts
Register evaluateOnNewDocument() before navigation when the page’s own JavaScript must observe your setup from the beginning. It runs after a document is created but before scripts belonging to that document execute, and it also applies when child frames are attached or navigated.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
const scriptId = await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en'],
});
window.__automationReady = true;
});
await page.goto('https://example.com', {waitUntil: 'domcontentloaded'});
console.log(await page.evaluate(() => window.__automationReady));
await page.removeScriptToEvaluateOnNewDocument(scriptId);
} finally {
await browser.close();
}
The returned identifier lets you remove the registration when it is no longer needed. Calling addScriptTag() after goto() is too late for requirements that depend on running before the site’s scripts.
Rank #4
Inject into an iframe
Page methods target the main frame. An iframe has its own document and JavaScript context, so find the intended Frame and call its method.
const frame = page.frames().find(frame => frame.url().includes('/widget'));
if (!frame) {
throw new Error('Widget frame not found');
}
await frame.addScriptTag({
content: 'window.widgetReady = true;',
});
const ready = await frame.evaluate(() => window.widgetReady);
console.log(ready);
The URL predicate is site-specific. For a dynamically created iframe, wait until it appears before selecting it:
await page.waitForFunction(() =>
[...document.querySelectorAll('iframe')].some(frame => frame.src.includes('/widget'))
);
const frame = page.frames().find(frame => frame.url().includes('/widget'));
if (!frame) throw new Error('Widget frame not found');
await frame.addScriptTag({content: 'window.widgetReady = true;'});
Cross-origin status does not prevent Puppeteer from addressing a frame through its Frame object, but selectors and page state must be evaluated in that frame’s own context. Do not use a main-frame selector or page.evaluate() and assume it refers to the iframe.
Free tools Windows power users keep installed
One-click scans. No signup required.
Best Value
- Used Book in Good Condition
Complete pattern with cleanup and error handling
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.evaluateOnNewDocument(() => {
window.__testRun = Date.now();
});
await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 30_000,
});
await page.addScriptTag({path: './custom.js'});
await page.evaluate(value => {
document.body.dataset.run = String(value);
}, await page.evaluate(() => window.__testRun));
console.log(await page.title());
} catch (error) {
console.error('Puppeteer script injection failed:', error);
process.exitCode = 1;
} finally {
await browser.close();
}
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Troubleshooting custom script injection
“Cannot find module” or a missing local file
- Cause: The
pathis resolved fromprocess.cwd(). - Fix: Log
process.cwd(), start the process from the expected directory, or pass a correctly constructed absolute path.
The script runs too late
- Cause:
addScriptTag()was called after navigation or after the application initialized. - Fix: Register
evaluateOnNewDocument()beforegoto()for pre-script setup.
A variable is undefined inside evaluate()
- Cause: Browser-side functions cannot access Node.js closures.
- Fix: Pass every required value as an argument, and define browser-only helpers inside the evaluated function.
A remote script does not load
- Cause: DNS, network policy, the remote server, or the page’s policy blocked the request.
- Fix: Open the URL in the same browser context, inspect console and network events, and prefer a local
pathwhen you control the source.
The iframe cannot be found
- Cause: It has not been attached yet, its URL changed, or the predicate matches the wrong frame.
- Fix: Wait for the iframe, print
page.frames().map(frame => frame.url()), and use a predicate that identifies the intended frame.
Later steps race the injection
- Cause: A promise from
addScriptTag(),goto(), orevaluate()was not awaited. - Fix: Await every operation whose result or side effect is required by the next step.
Performance, reliability, and security considerations
- Prefer the smallest API: Use
evaluate()for a one-off read or mutation; inserting a full script element is unnecessary overhead for a single expression. - Install early only when necessary: New-document hooks run on every matching new document and child-frame navigation, so keep them short and remove them when the browser session no longer needs them.
- Make injection idempotent: Navigation, retries, and SPA transitions can cause your orchestration code to run more than once. Use an ID or a page flag to prevent duplicate listeners.
- Control untrusted input: Do not concatenate untrusted strings into inline JavaScript. Pass data as arguments to
evaluate(), validate URLs, and treat injected code as equivalent to code execution in the target page. - Choose a clear navigation condition:
domcontentloadedis often sufficient for DOM injection; wait for a selector or network state when the script depends on application data. Set a finite timeout and handle navigation failures.
Or skip the browser setup
If your actual goal is to obtain a clean rendered image or PDF rather than run a custom Puppeteer workflow, ScreenshotNeo provides a single HTTP request. Its capture pipeline accepts consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.
See the ScreenshotNeo documentation for the complete parameter set. The basic cURL call is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo also exposes an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. It supports full-page and element captures, device presets, custom CSS and JavaScript, waits, request blocking, authentication headers and cookies, PDFs, resizing, caching, signed links, asynchronous jobs, bulk capture, and a usage API. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Which method should you use?
- Choose
addScriptTag({path})for a local file,addScriptTag({content})for inline source, oraddScriptTag({url})for a browser-loadable remote script. - Choose
evaluate()when you need a one-off operation and do not need a script element. - Choose
evaluateOnNewDocument()when timing before page scripts matters. - Choose a
Framemethod when the target is an iframe rather than the main document.
Frequently Asked Questions
Does page.addScriptTag() execute in Node.js?
No. It inserts a script into the browser document and runs there. Node.js values must be passed explicitly to page.evaluate() or embedded in the injected source.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsCan I add a script before calling page.goto()?
For code that must run before the site’s scripts, register page.evaluateOnNewDocument() before navigation. addScriptTag() is intended for insertion into the current document and does not provide that pre-script timing guarantee.
How do I remove a script added with addScriptTag()?
Keep the returned element handle and remove the DOM element in the page context, or use an ID and remove the matching element. New-document registrations are removed with removeScriptToEvaluateOnNewDocument() using its returned identifier.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




