Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesUse Chrome DevTools Protocol (CDP) to automate a browser that visits each URL, captures the rendered page with Page.captureScreenshot, decodes its base64 response, and saves one PNG per URL. CDP handles each navigation and capture; your script supplies the URL loop, readiness checks, filenames, and error handling.
What the workflow does
For each URL, your automation attaches to a Chrome or Chromium page target, enables the Page domain, navigates with Page.navigate, waits for the page state you need, then calls Page.captureScreenshot with PNG output. The screenshot response contains base64 image data, which must be decoded before it is written as a binary .png file. See the Chrome DevTools Protocol Page domain reference for method parameters and response fields.
The protocol gives you control over browser-level operations, but it does not provide a universal definition of “ready.” A page may still be loading application data or changing after navigation events have fired. Select a readiness condition that fits the sites you capture.
Capture a URL list directly through CDP
One practical way to call CDP methods is through Puppeteer’s CDP session API. The example below uses Node.js, connects to a Chrome instance exposing its remote debugging endpoint, and captures URLs sequentially. Sequential capture makes it easier to associate a navigation or file error with the URL that caused it.
#1 Best Overall
Prerequisites and setup
- Install a current Node.js release and Puppeteer:
npm install puppeteer. - Start Chrome or Chromium with remote debugging enabled, then provide its debugging WebSocket URL in
CDP_WS_ENDPOINT. Protect this endpoint: anyone who can connect may be able to control the browser. - Set
SCREENSHOT_URLSto a comma-separated list of URLs. The output directory is created automatically.
Runnable Node.js example
const fs = require('node:fs/promises');
const path = require('node:path');
const puppeteer = require('puppeteer');
const endpoint = process.env.CDP_WS_ENDPOINT;
const urls = (process.env.SCREENSHOT_URLS || '')
.split(',')
.map((url) => url.trim())
.filter(Boolean);
const outputDir = process.env.OUTPUT_DIR || 'screenshots';
if (!endpoint) throw new Error('Set CDP_WS_ENDPOINT to Chrome’s debugging WebSocket URL.');
if (urls.length === 0) throw new Error('Set SCREENSHOT_URLS to a comma-separated URL list.');
function filenameFor(url, index) {
let host = 'page';
try { host = new URL(url).hostname.replace(/[^a-z0-9.-]/gi, '_'); } catch {}
return `${String(index + 1).padStart(3, '0')}-${host}.png`;
}
(async () => {
await fs.mkdir(outputDir, { recursive: true });
const browser = await puppeteer.connect({ browserWSEndpoint: endpoint });
const page = await browser.newPage();
const session = await page.createCDPSession();
await session.send('Page.enable');
const failures = [];
try {
for (const [index, url] of urls.entries()) {
const file = path.join(outputDir, filenameFor(url, index));
try {
const navigation = await session.send('Page.navigate', { url });
if (navigation.errorText) throw new Error(navigation.errorText);
// This is a baseline, not proof that app-specific content is ready.
await page.waitForFunction(() => document.readyState === 'complete', { timeout: 30000 });
// Replace/add an application-specific wait when the page renders dynamically.
const result = await session.send('Page.captureScreenshot', { format: 'png' });
await fs.writeFile(file, Buffer.from(result.data, 'base64'));
console.log(`Saved ${url} -> ${file}`);
} catch (error) {
failures.push({ url, file, error: error.message });
console.error(`Failed ${url}: ${error.message}`);
}
}
} finally {
await session.detach().catch(() => {});
await page.close().catch(() => {});
await browser.disconnect();
}
if (failures.length) {
console.error('Failures:', failures);
process.exitCode = 1;
}
})();
Set the environment variables before running the script. For example, on macOS or Linux:
export CDP_WS_ENDPOINT='ws://127.0.0.1:9222/devtools/browser/REPLACE_WITH_BROWSER_ID'
export SCREENSHOT_URLS='https://example.com,https://example.org'
node capture-list.js
The endpoint shown is illustrative; use the actual WebSocket URL exposed by the Chrome instance you started. Do not expose a remote-debugging port to an untrusted network.
Rank #2
- Intuitive interface of a conventional FTP client
- Easy and Reliable FTP Site Maintenance.
- FTP Automation and Synchronization
Important navigation details
Page.navigate returns a frame identifier and may return a loaderId; same-document navigations can omit that identifier. When navigation fails, the response can include errorText. The example checks this field and records failures per URL rather than silently saving a screenshot as though navigation succeeded. It also checks the document’s ready state, but single navigation events or a completed document state are not a guarantee that a JavaScript application has finished rendering.
For a dynamic page, wait for a stable, page-specific signal before capture—for example, a known results container or a loading indicator disappearing. If you capture authenticated pages, configure the browser session with the required login state before navigating. CDP itself does not sign in for you.
Rank #3
Choose the screenshot area and format
Page.captureScreenshot supports png, jpeg, and webp; PNG is the documented default. The method’s clip option can capture a specified region. Decide whether you need the visible viewport, a clipped area, or content beyond it, and validate the result against the target pages. captureBeyondViewport is documented as experimental, so check the protocol version supported by the Chrome you are actually using before depending on it.
The example intentionally captures the page’s current browser screenshot state without a clip. It does not promise a full-page image: choose and test the appropriate capture behavior for your Chrome version and use case rather than assuming content outside the viewport will be included.
Rank #4
Make the batch reliable
Use stable, unique filenames
The sample prefixes filenames with the list position and hostname, so two URLs on the same host do not overwrite one another. For repeated runs, consider adding a timestamp or organizing output by run. If URLs may contain sensitive query values, avoid placing the full URL in filenames or logs.
Keep failures isolated
Navigation errors, readiness timeouts, screenshot errors, and filesystem failures can affect individual URLs. The loop records each failure and proceeds to the next URL; it exits with a nonzero status at the end if any capture failed. This lets a scheduler or calling process detect an incomplete batch while retaining successful files.
Best Value
Handle readiness and consistency deliberately
- Use a selector or application signal when the screenshot depends on asynchronously loaded content.
- Use a consistent viewport and browser state across captures if you need comparable screenshots.
- Test lazy-loaded pages and long pages specifically; the protocol reference does not establish a single universal full-page behavior or readiness timeout.
- Do not infer a universal concurrency limit or capture speed. The protocol documentation does not specify one; measure your own workload before adding parallel tabs or browsers.
Direct CDP or Puppeteer?
| Approach | Best fit | Trade-off |
|---|---|---|
| Direct CDP methods | You need explicit control over protocol methods and fields such as Page.navigate and screenshot options. |
You must manage the session, navigation results, readiness, base64 decoding, output files, and errors yourself. |
| Puppeteer | Your team already uses Node.js browser automation and wants convenient page navigation and screenshot-file workflows. | It is a higher-level library; exact protocol options and experimental fields still depend on the Chrome version and CDP support. |
Puppeteer’s official guides document browser pages and its screenshot workflow: Puppeteer screenshots and Puppeteer Browser API. The example above uses Puppeteer to connect to Chrome while issuing the navigation and screenshot commands through a CDP session.
Common errors and fixes
- Cannot connect to browser: confirm Chrome is running with remote debugging enabled and that
CDP_WS_ENDPOINTis the browser WebSocket endpoint, not a page target URL or an example placeholder. - Navigation response contains
errorText: the browser reported a navigation failure. Check the URL, network access, redirects, and whether the destination is reachable from the machine running Chrome; retain the URL and error for a targeted retry. - Screenshot is blank or incomplete: capture may have occurred before the application rendered its content. Replace the generic ready-state wait with a selector or other site-specific readiness condition, and inspect the page in the same browser session.
- Screenshot file cannot be written: check that the output path is writable and that there is sufficient space. The script creates the directory, but it cannot fix permission or storage errors.
- Same-page navigation behaves differently: same-document navigation may not include a
loaderId. Do not treat its absence alone as a failed navigation; inspecterrorTextand wait for the page condition relevant to the capture. - Experimental option is rejected: verify that the connected Chrome’s protocol version supports the parameter. In particular,
captureBeyondViewportis experimental in the reference.
Or skip the browser setup
ScreenshotNeo offers a one-request screenshot API and an MCP server for AI agents. A GET request can return a PNG, JPEG, WebP, or PDF; its documentation is at ScreenshotNeo API docs.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Before the capture, ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response indicates the page verdict and billing status. AI agents can use its MCP server tools to take screenshots, inspect page information, and capture PDFs. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo’s free plan to try 1,000 screenshots a month with no card.
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 minutePC 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 & 11Frequently Asked Questions
Can I save the CDP screenshot response directly as a PNG?
No. Decode the response’s base64 `data` value to binary bytes before writing the `.png` file.
Does CDP wait until every dynamic element has loaded?
No. Readiness depends on the site and on what the capture must show; wait for an application-specific condition when necessary.
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.

