Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows 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 reinstallUse page.setContent(html) when each loop iteration supplies a complete HTML document; use page.evaluate() when you need to change part of an already loaded page. Await the update and screenshot in each iteration, and give every image a unique filename. The examples below show both approaches with Puppeteer and Playwright.
Choose whether to replace the document or update the page
The right method depends on what changes between screenshots:
- Each state is a standalone HTML document: render that state as HTML and call
page.setContent(html)before capturing it. - The page is already loaded and only some content changes: call
page.evaluate()to update the relevant DOM or application state, then capture.
setContent is not just a shortcut for changing one element. Playwright documents that it uses document.write() semantics, so it replaces the page content rather than preserving a live application as-is. See the Playwright Page API. Puppeteer also exposes setContent for setting page content in its Page.setContent API reference.
Set up a Node.js project
Choose one library for the script. Both examples below use a locally launched browser and write numbered PNG files into a screenshots directory.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problems#1 Best Overall
Install Puppeteer
npm init -y
npm install puppeteer
Or install Playwright
npm init -y
npm install playwright
npx playwright install chromium
The install command obtains Playwright’s Chromium browser. If your project already provisions browsers in its environment, follow that environment’s setup instead. Puppeteer and Playwright APIs evolve; check the linked official references when matching code to a particular installed version.
Capture a screenshot for each complete HTML state
This Puppeteer script creates a page once, sets a viewport, replaces its content for each input, and waits for the screenshot before moving to the next item. Save it as capture.js, then run node capture.js.
const fs = require('node:fs/promises');
const puppeteer = require('puppeteer');
const items = [
{ title: 'First card', color: '#e9f2ff' },
{ title: 'Second card', color: '#fff1df' },
{ title: 'Third card', color: '#e8f7ec' },
];
function escapeHtml(value) {
return String(value).replace(/[<>&"']/g, (character) => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
})[character]);
}
function renderHtml(item) {
return `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { margin: 0; font: 16px system-ui, sans-serif; }
main { box-sizing: border-box; min-height: 100vh; padding: 48px; background: ${item.color}; }
h1 { margin: 0; }
</style>
</head>
<body><main><h1>${escapeHtml(item.title)}</h1></main></body>
</html>`;
}
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage({
viewport: { width: 1280, height: 800 },
});
await fs.mkdir('screenshots', { recursive: true });
for (let i = 0; i < items.length; i++) {
await page.setContent(renderHtml(items[i]));
const filename = `screenshots/shot-${String(i + 1).padStart(3, '0')}.png`;
await page.screenshot({ path: filename });
process.stdout.write(`Saved ${filename}n`);
}
} finally {
await browser.close();
}
})();
The escaping helper treats each title as text rather than trusted markup, preventing characters such as < or & from being interpreted as HTML. In real applications, use your existing HTML templating and escaping strategy. The code keeps the viewport constant, so each capture uses the same visible dimensions. To include content beyond the viewport, use Puppeteer’s fullPage screenshot option, described in its screenshots guide.
Playwright version
The same loop works with Playwright. Replace the Puppeteer import and launch/page setup with Playwright’s API; here is a complete equivalent to save as capture-playwright.js:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #2
const fs = require('node:fs/promises');
const { chromium } = require('playwright');
const items = [
{ title: 'First card', color: '#e9f2ff' },
{ title: 'Second card', color: '#fff1df' },
{ title: 'Third card', color: '#e8f7ec' },
];
function escapeHtml(value) {
return String(value).replace(/[<>&"']/g, (character) => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
})[character]);
}
function renderHtml(item) {
return `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { margin: 0; font: 16px system-ui, sans-serif; }
main { box-sizing: border-box; min-height: 100vh; padding: 48px; background: ${item.color}; }
h1 { margin: 0; }
</style>
</head>
<body><main><h1>${escapeHtml(item.title)}</h1></main></body>
</html>`;
}
(async () => {
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage({
viewport: { width: 1280, height: 800 },
});
await fs.mkdir('screenshots', { recursive: true });
for (let i = 0; i < items.length; i++) {
await page.setContent(renderHtml(items[i]));
const filename = `screenshots/shot-${String(i + 1).padStart(3, '0')}.png`;
await page.screenshot({ path: filename });
process.stdout.write(`Saved ${filename}n`);
}
} finally {
await browser.close();
}
})();
Playwright’s page.screenshot() accepts a path and exposes options including image type and full-page capture; see the Page API. The two scripts are alternatives, not code to run together.
Update an existing page without replacing its HTML
For a page that should retain its document and change one value per iteration, use evaluate. The function runs in the browser page context; ordinary variables from Node.js are not automatically visible there. Pass each changing item as an argument. Puppeteer documents argument passing and asynchronous evaluation in its Page.evaluate API; Playwright explains the separation between Node.js and page contexts in Evaluating JavaScript.
for (let i = 0; i < items.length; i++) {
await page.evaluate((item) => {
document.querySelector('#preview').textContent = item.label;
document.querySelector('#preview').style.backgroundColor = item.color;
}, items[i]);
const filename = `screenshots/shot-${String(i + 1).padStart(3, '0')}.png`;
await page.screenshot({ path: filename });
}
This snippet assumes page is already open on a document containing an element with id="preview", and items contains label and color values. If the selector does not match anything, the page-side code needs to handle that case or the update will fail. For application frameworks, changing the framework’s state through its supported interface is generally safer than making direct DOM edits that the framework may overwrite.
Make each capture represent the intended visual state
Awaiting setContent or evaluate and then awaiting screenshot prevents the loop from advancing before those operations finish. It does not guarantee that every external font, image, stylesheet, animation, or application request has reached the exact visual state you want. The APIs do not establish one readiness rule that fits every page.
- Static, self-contained HTML: set the content and capture it. Inline CSS and data that do not depend on external resources make the result easier to reproduce.
- External images or fonts: wait for the specific resource or a page-side readiness signal before capturing. For images, you can check that relevant elements are complete and have natural dimensions; decide how to treat broken images instead of waiting indefinitely.
- Dynamic application: wait for an app-specific selector or explicit ready marker that appears only after the state is rendered. A fixed sleep can be too short on a slow run and unnecessarily long on a fast one.
- Navigation to a URL: wait for a condition appropriate to that site. Puppeteer’s screenshot guide demonstrates navigation with
page.goto(url, { waitUntil: 'networkidle2' }), but that is an example for navigation, not a universal rule for every page or forsetContent.
If the page uses animation or time-dependent content, make that deterministic where possible—for example, disable animation in the capture stylesheet or freeze the data used to render the state. Otherwise two screenshots of the same input may differ even though the loop is working as written.
Choose viewport, full-page, or element capture
| Capture goal | Approach | Trade-off |
|---|---|---|
| Visible viewport | Set the viewport and call page.screenshot({ path }). |
Content below the viewport is not included. |
| Entire page | Use the library’s full-page screenshot option. Playwright documents fullPage in its Page API. |
A long document produces a taller image; it is not the same as a viewport capture. |
| One element | Locate the element and capture its bounding box or use the library’s element screenshot API. Puppeteer’s screenshot guide demonstrates element screenshots. | The target must exist and be visible in the state being captured. |
For device-sized output, set the viewport to the dimensions you want before rendering. A device scale factor changes pixel density; it does not change the CSS layout dimensions in the same way as choosing a different viewport. Puppeteer and Playwright expose different option details, so consult the relevant API for the installed library rather than copying an option name between them.
Keep loop output ordered and recover cleanly from errors
Use for with await when each state must be captured before the next is applied. Avoid items.forEach(async item => ...) when capture order matters: the outer function does not wait for those callbacks. Every output path should be unique. Screenshot APIs accept paths, but do not enforce uniqueness, so reusing a filename overwrites an earlier image.
A browser in a finally block closes even if rendering or saving throws. For batch work where one bad item should not stop the rest, wrap the work for each item in its own try/catch, record the failed item’s index and error, and decide whether to continue; keep the outer finally around the browser close. Do not mutate the same page from concurrent tasks. If you need parallel work, use separate pages and distinct output paths, and account for the extra browser resources.
Recommended Free Tools
Rank #4
Troubleshooting common loop failures
Every output file contains the same state
Check that the state update is awaited and completes before the screenshot call. In an existing page, confirm the selector matches and the application has rendered the new value. Do not fire asynchronous callbacks with forEach and assume the loop waits for them.
Some images or fonts are missing
The content-setting call can finish before external assets have produced the appearance you expect. Wait for the specific assets or a known ready condition, and check that the browser can access their URLs. An image that is blocked or returns an error will not be repaired by increasing an arbitrary fixed delay.
The page is blank or content appears clipped
Verify that the generated HTML includes the expected body content, that the viewport matches the intended capture, and that you chose viewport rather than full-page capture when content extends below the fold. For a targeted capture, check that the element exists and is visible.
Earlier screenshots disappear
Make the filename depend on the iteration or a stable item identifier. If filenames can contain user-provided text, sanitize that text before using it as a path. A unique name per item avoids accidental replacement.
Best Value
The process remains open after a failure
Put browser.close() in a finally block that runs after the browser is successfully launched. If launch itself fails, there is no browser instance to close; if the failure occurs inside the loop, finally still handles cleanup.
Or skip the browser setup
If you need a hosted screenshot of a URL rather than a local browser loop that mutates arbitrary page state, ScreenshotNeo is a website screenshot API and MCP server. It removes cookie-consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are not billed. Its MCP server provides screenshot tools for AI agents. The API call below saves a screenshot response; see the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
For a one-off request, ScreenshotNeo includes 1,000 screenshots per month on its free plan with no card required; paid plans start at $5 for 3,000. This API call captures a URL; it is not a substitute for changing arbitrary DOM state in a locally controlled page. Sign up for the free plan.
Documentation version context
The Puppeteer setContent page displayed version 25.11.0 in the reviewed documentation, while its screenshot guide and screenshot reference displayed 25.12.0. Those page labels should not be read as a synchronized release statement. The reviewed Playwright Page API is rolling documentation, and the evaluating-JavaScript page is labeled “Next.” Check the documentation corresponding to the package version in your project before depending on version-specific options.
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.

