What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Use page.evaluate() in Playwright or Page.evaluate() in Puppeteer. The callback runs inside the loaded page, where window and document exist; the value you return crosses back to your automation script. The two environments do not share lexical variables, so pass every outside value as an explicit argument. Await the call when the callback is asynchronous.
The execution model: two JavaScript worlds
A headless test has an automation process and a browser page. Your Node.js (or other binding) code controls navigation and calls an evaluation method. The callback itself executes in the page’s JavaScript context, with browser globals such as window, document, location, and the page’s DOM available. Playwright documents this boundary in its JavaScript evaluation guide; Puppeteer’s equivalent is Page.evaluate().
Do not treat the callback as a closure over your automation script. A variable declared outside is not visible inside unless you pass it:
const selector = 'h1';
const text = await page.evaluate((css) => {
return document.querySelector(css)?.textContent ?? null;
}, selector);
Values crossing the boundary should be serializable data: strings, numbers, booleans, arrays, or plain objects. A DOM node is a live browser object, not an ordinary JSON result. Use an evaluation handle when you need to keep and manipulate that object in the page.
#1 Best Overall
Playwright: a complete evaluation example
The following script navigates, reads the title and first heading, passes a selector into the page, performs asynchronous page-side work, and closes the browser. It assumes Playwright is already installed in your project.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
const pageTitle = await page.evaluate(() => document.title);
const heading = await page.evaluate((selector) => {
return document.querySelector(selector)?.textContent?.trim() ?? null;
}, 'h1');
const status = await page.evaluate(async () => {
const response = await fetch(location.href);
return response.status;
});
console.log({ pageTitle, heading, status });
await browser.close();
})();
The first callback reads a browser global. The second receives 'h1' as its argument and returns a string or null. The third is marked async; Playwright waits for its returned promise and gives your script the resolved HTTP status. A failed navigation, blocked request, or page-side exception still rejects the surrounding operation, so catch errors where your application can report or recover from them.
Passing multiple arguments
Pass one serializable value or an object containing several values. This keeps the boundary explicit and avoids accidentally depending on automation-side state.
const result = await page.evaluate(({ selector, suffix }) => {
const node = document.querySelector(selector);
return node ? `${node.textContent.trim()}${suffix}` : null;
}, { selector: '.price', suffix: ' USD' });
Evaluating in a frame
If the content you need lives in an iframe, obtain that frame from Playwright and call its evaluation method. The same context rules apply: code runs in that frame, and values must be passed and returned explicitly. A selector in the top-level page cannot directly query a different frame’s document.
Puppeteer: the equivalent API
Puppeteer exposes the same concept through Page.evaluate(). Its current API reference is versioned (the reference retrieved for this article reports 25.12.0), so check the current method documentation when upgrading.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
const title = await page.evaluate(() => document.title);
const heading = await page.evaluate((selector) => {
return document.querySelector(selector)?.textContent?.trim() ?? null;
}, 'h1');
const status = await page.evaluate(async () => {
const response = await fetch(location.href);
return response.status;
});
console.log({ title, heading, status });
await browser.close();
})();
Puppeteer also waits for a promise returned by the callback. The API spelling and argument shape are nearly identical to Playwright’s, so the decision should follow your project’s runtime, browser setup, and existing automation code rather than an assumed universal performance winner. Chrome for Developers describes Puppeteer as a high-level browser automation API; its supported browser details remain release-sensitive.
Returning data versus keeping a page object
Use ordinary evaluation when you need a value that can leave the page:
- text, attributes, URLs, dimensions, and computed numbers;
- arrays or plain objects assembled from several elements;
- a status code or other result from asynchronous page-side work.
For a live DOM element or another JavaScript object that must remain in the browser, use a handle. Puppeteer’s JavaScript-execution guide explains that returning an element through normal serialization does not create a usable live element reference; use evaluateHandle() or an ElementHandle. Playwright provides evaluateHandle() for the same purpose. Dispose of handles when your framework requires explicit cleanup.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated 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// Playwright: keep the object in the browser instead of serializing it
const elementHandle = await page.evaluateHandle(() => document.querySelector('h1'));
// Use the handle with the framework's handle APIs, then dispose it when finished.
await elementHandle.dispose();
Do not return a function, a DOM node, or a complex browser object when your caller expects JSON-like data. Extract the properties you need inside the callback and return a small plain object instead.
Choose the browser mode deliberately
“Headless” is not one identical implementation. Playwright documents both a Chromium headless shell and a newer Chromium mode selected through the chromium channel. The newer mode uses the real Chrome browser, while the shell can differ in rendering and behavior. See the Playwright browser guide.
If your production issue depends on Chrome-specific behavior, run the channel that matches production and record that choice in your test configuration. If you are validating only data extraction, either mode may be sufficient, but you should still keep the mode stable between runs. Puppeteer likewise depends on the browser binary it launches; the exact binary and version are part of the result, not incidental details.
Playwright and Puppeteer compared for evaluation
| Concern | Playwright | Puppeteer |
|---|---|---|
| Evaluation method | page.evaluate(callback, arg) |
page.evaluate(callback, arg) |
| Where callback runs | In the page’s JavaScript context | In the page’s JavaScript context |
| Async callback | Returned promises are awaited | Returned promises are awaited |
| Live object reference | evaluateHandle() |
evaluateHandle() or ElementHandle |
| Browser-mode consideration | Headless shell versus real Chrome channel is documented | Behavior follows the launched browser binary and version |
| Best fit | Projects already using Playwright’s browser and context controls | Projects already using Puppeteer’s Chrome-oriented automation API |
The documented evaluation behavior is substantially the same. Compare the surrounding project APIs, supported language/runtime setup, and browser binary you must operate; the available material does not establish a universal speed or reliability ranking.
Reliable evaluation patterns
Wait for the state you actually inspect
Evaluation runs against the page state that exists at the moment of the call. Navigate first, then wait for the DOM, a specific selector, or the application state your check requires. A callback that queries an element before client-side rendering finishes can correctly return null even though the element appears a moment later.
Keep callbacks small
Do DOM traversal and page-side computation in one callback when that reduces round trips, but return only the data the automation process needs. Smaller results serialize faster and are easier to log and validate.
Handle failures at the automation boundary
Wrap navigation and evaluation in your test runner’s error handling. Distinguish a missing element (a valid null result in the example) from a thrown page exception, a rejected promise, or a browser crash. Include the URL, selector, browser mode, and a short operation name in your error record.
Clean up pages, browsers, and handles
Close pages and the browser in a finally block in long-running jobs. Dispose of evaluation handles after use. This prevents detached browser processes and accumulated remote objects from degrading later evaluations.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsTroubleshooting common errors
“ReferenceError: selector is not defined”
Cause: the callback attempted to read a variable from the automation script’s lexical scope.
Fix: pass it as an argument, for example page.evaluate((css) => document.querySelector(css), selector).
The result is null
Cause: the selector did not match in that document or the page had not rendered the element yet.
Fix: verify the selector in the correct frame and wait for the page state your application promises before evaluating.
Rank #4
A returned element is an empty object or unusable value
Cause: DOM objects are not ordinary serializable data.
Fix: return text or attributes, or retain the object with evaluateHandle()/ElementHandle.
An async callback returns too soon
Cause: the promise was not returned or awaited inside the callback.
Fix: mark the callback async and return the awaited operation, as in return (await fetch(location.href)).status.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →The same code behaves differently in CI and locally
Cause: different browser binaries, headless modes, versions, viewport settings, or page timing.
Fix: pin and report the browser mode/version, use the mode matching your target environment, and wait for a deterministic readiness condition.
Evaluation fails after navigation
Cause: navigation failed, the page closed, a frame was replaced, or page-side code threw an exception.
Fix: capture the navigation error, confirm the page is still open, reacquire a replaced frame, and log the original page exception instead of masking it with a generic timeout.
When you only need a clean visual capture
If your goal is a screenshot or PDF rather than a value returned from page JavaScript, ScreenshotNeo removes the browser setup. One GET request captures a URL as PNG, JPEG, WebP, or PDF. It accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; those steps can be turned off. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response reports its page verdict and billing status in X-Page-Verdict and X-Billed headers.
Or skip the browser setup
Use the documented endpoint and options at ScreenshotNeo’s API documentation:
Recommended Free Tools
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
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 provides an MCP server for AI agents such as Claude, Cursor, and other MCP clients, with take_screenshot, get_page_info, and capture_pdf tools. Every feature is on every plan: 1,000 screenshots per month are free with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.
Frequently asked questions
Can the same evaluation callback be reused on a frame?
Yes. Call the frame’s evaluation method rather than the top-level page’s method, and pass arguments exactly as you would for the page. The callback then sees that frame’s document.
Does Puppeteer run only in headless mode?
No. Headless is a launch choice. Puppeteer can drive a visible browser as well; the important point for reproducibility is to record the binary, version, and mode used.
Where should I check for release-specific behavior?
Use the Playwright evaluation and browser guides and Puppeteer’s versioned API and JavaScript-execution guides linked above before upgrading, because evaluation and headless-browser details can change with releases.
Frequently Asked Questions
Can the same evaluation callback be reused on a frame?
Yes. Call the frame’s evaluation method rather than the top-level page’s method, and pass arguments exactly as you would for the page. The callback then sees that frame’s document.
Does Puppeteer run only in headless mode?
No. Headless is a launch choice. Puppeteer can drive a visible browser as well; record the binary, version, and mode for reproducible results.
Where should I check for release-specific behavior?
Check the linked Playwright and Puppeteer documentation before upgrades; evaluation and headless-browser details are release-sensitive.
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.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →

