Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteUse one Puppeteer browser, open each URL in its own page, set a consistent viewport before navigation, wait for the page to be ready, and call page.screenshot({ fullPage: true }). The script below saves every site to a unique file, handles cleanup, and includes practical fixes for lazy loading, cookie dialogs, very tall pages, and concurrency.
What “full page” means in Puppeteer
Puppeteer’s Page.screenshot() method captures the rendered page. Its fullPage option is false by default; setting it to true captures the document beyond the current viewport. The current Puppeteer API pages identify the documentation version as 25.12.0 (2026). Use a deliberate viewport and readiness condition so screenshots from different websites are comparable.
A full-page capture is still a snapshot of a live page state. Ads, animations, consent dialogs, personalized content and time-based widgets can differ between runs. Puppeteer does not promise visual determinism.
Prerequisites and project setup
Install Node.js and Puppeteer
Use a maintained Node.js release and create a project with ECMAScript modules enabled:
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →#1 Best Overall
mkdir multi-site-shots && cd multi-site-shotsnpm init -ynpm install puppeteer- Add
"type": "module"topackage.json, or save the script with a module-compatible configuration.
The regular puppeteer package downloads a compatible Chrome for Testing during installation. In a restricted build environment, install or configure a browser separately and pass its executable path to puppeteer.launch().
Choose an output policy
- PNG is lossless and is the safest default for visual comparison.
- Use a unique filename for every URL. An index, sanitized hostname, or both prevents later captures from overwriting earlier ones.
- Create the destination directory before the loop.
Complete sequential script for multiple URLs
This is the simplest reliable pattern: one browser, one page at a time, and guaranteed cleanup even when navigation or capture fails.
import puppeteer from 'puppeteer';
import { mkdir } from 'node:fs/promises';
const urls = [
'https://example.com',
'https://news.ycombinator.com',
'https://developer.chrome.com'
];
const outputDir = 'screenshots';
function safeHost(url, index) {
const host = new URL(url).hostname.replace(/^www./, '');
const clean = host.replace(/[^a-z0-9.-]/gi, '-');
return `${String(index + 1).padStart(2, '0')}-${clean || 'site'}.png`;
}
await mkdir(outputDir, { recursive: true });
const browser = await puppeteer.launch();
try {
for (const [index, url] of urls.entries()) {
const page = await browser.newPage();
try {
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto(url, {
waitUntil: 'networkidle2',
timeout: 60_000
});
const filename = `${outputDir}/${safeHost(url, index)}`;
await page.screenshot({
path: filename,
fullPage: true,
type: 'png'
});
console.log(`Saved ${filename}`);
} catch (error) {
console.error(`Failed ${url}:`, error.message);
} finally {
await page.close();
}
}
} finally {
await browser.close();
}
Run it with node capture.js. The viewport is set before goto, so responsive breakpoints are selected consistently. networkidle2 waits until there are no more than two active network connections, which is a useful baseline for many pages but not a guarantee that an application has finished rendering.
Make readiness match the website
Wait for a page-specific selector
Single-page applications can reach network idle before their main content appears. Navigate with a normal load condition, then wait for an element that proves the page is usable:
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60_000 });
await page.waitForSelector('main article, [data-page-ready]', {
visible: true,
timeout: 30_000
});
await page.screenshot({ path: filename, fullPage: true });
Choose a selector that is present only after the relevant content is rendered. If the site exposes an application-ready promise or event, waiting for that signal is more accurate than an arbitrary sleep.
Rank #2
Use a short delay only when it has a reason
Some pages render a chart, font, or animation shortly after their main selector appears. In that case, add a bounded delay:
await new Promise(resolve => setTimeout(resolve, 1_500));
Document the reason and keep the delay as short as the page allows. A long fixed sleep slows every URL and still may not cover a slower run.
Wait for fonts and images
Before capturing, you can wait for fonts and currently known images:
await page.evaluate(async () => {
if (document.fonts?.ready) await document.fonts.ready;
await Promise.all([...document.images].map(img => {
if (img.complete) return Promise.resolve();
return new Promise(resolve => {
img.addEventListener('load', resolve, { once: true });
img.addEventListener('error', resolve, { once: true });
});
}));
});
This does not automatically trigger images that use viewport-based lazy loading. Handle those explicitly.
Load lazy content before the screenshot
Images or cards may be requested only after they enter the viewport. Scroll through the document, allowing the browser to trigger its lazy-loading behavior, then return to the top and capture:
await page.evaluate(async () => {
await new Promise(resolve => {
let y = 0;
const step = Math.max(window.innerHeight, 600);
const timer = setInterval(() => {
window.scrollBy(0, step);
y += step;
if (y >= document.body.scrollHeight) {
clearInterval(timer);
window.scrollTo(0, 0);
resolve();
}
}, 100);
});
});
await new Promise(resolve => setTimeout(resolve, 500));
await page.screenshot({ path: filename, fullPage: true });
For pages that append content while scrolling, recalculate the height inside the loop and stop after a safety limit. Scrolling can also change sticky navigation and trigger animations, so inspect a sample output.
Control consent dialogs, popups and fixed UI
A cookie banner, newsletter modal or chat bubble can obscure the page. If the site offers no stable dismissal flow, hide known selectors immediately before capture:
Outdated 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 matchWindows 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 reinstallawait page.addStyleTag({
content: `
.cookie-banner, .newsletter-modal, .chat-widget,
[aria-label*="chat" i] { display: none !important; }
`
});
Prefer clicking a real consent button when the page must record a choice. Do not hide elements blindly when the purpose of the screenshot is to document the unmodified visitor experience.
Very tall pages and sticky elements
Native fullPage: true is appropriate for ordinary documents, but extremely tall pages and viewport-relative elements can produce artifacts or consume substantial memory. The third-party puppeteer-full-page-screenshot package (version 1.2.5 on its 2024 npm listing) works around some tall-page and viewport-relative-element problems by taking multiple screenshots and merging them. Its documentation warns that scrolling may make sticky elements appear repeatedly. If that matters, temporarily hide or reset fixed and sticky elements before capture, or use the package’s approach and verify the merged result.
For a native capture, inspect position: fixed and position: sticky elements, reduce unnecessary resources, and test the tallest page in your set. A very large PNG can also exceed downstream upload or image-viewer limits; choose JPEG or WebP only when their compression trade-offs are acceptable.
Output formats and capture options
Puppeteer supports screenshot options such as path, fullPage, and image type settings. Keep the same viewport, device scale factor and format for a comparison set. A higher device scale factor produces more pixels and sharper text but increases memory and file size. If you need a transparent result for a page with a transparent background, set the page background accordingly and verify that the chosen format preserves it.
Sequential versus bounded concurrency
Sequential capture
The example’s one-page-at-a-time loop is the safest default. It limits memory, avoids competing downloads, preserves output order and makes failures easy to associate with a URL.
Bounded concurrency
Opening several pages can improve throughput, but unrestricted parallelism can exhaust CPU, memory, file descriptors or the target site’s capacity. Use a small worker pool and keep a result record for every URL:
const concurrency = 3;
let next = 0;
async function worker() {
while (true) {
const index = next++;
if (index >= urls.length) return;
const page = await browser.newPage();
try {
await page.setViewport({ width: 1440, height: 900 });
await page.goto(urls[index], { waitUntil: 'networkidle2', timeout: 60_000 });
await page.screenshot({
path: `${outputDir}/${safeHost(urls[index], index)}`,
fullPage: true
});
} finally {
await page.close();
}
}
}
await Promise.all(Array.from({ length: concurrency }, worker));
Start with a low limit, measure memory and completion time in your own environment, and reduce concurrency when pages are media-heavy or unstable. If ordering is important, encode the original index in each filename rather than relying on completion order.
Rank #4
Authentication, headers and repeatability
For private pages, establish authentication before navigation with a supported login flow, cookies or request headers, and keep credentials out of source control. A consistent user agent, timezone and viewport can reduce accidental layout differences, but personalized content may still vary. Record the URL, timestamp, viewport, readiness rule and browser version alongside each output when screenshots are used in a visual-regression process.
Free tools Windows power users keep installed
One-click scans. No signup required.
Troubleshooting Puppeteer screenshot failures
The image contains only the viewport
Cause: fullPage was omitted or set to false. Fix: call page.screenshot({ path, fullPage: true }). The option’s documented default is false.
The screenshot is blank or navigation times out
Cause: the URL is unreachable, blocks the browser, needs authentication, or never becomes idle because of long-lived connections. Verify the URL from the same runtime, increase the timeout only when justified, and replace networkidle2 with domcontentloaded plus a page-specific selector for streaming applications. Log the error and continue or fail the batch according to your requirements.
Content is missing below the fold
Cause: lazy loading has not been triggered. Scroll through the page, wait for newly requested content, and then capture. For infinite feeds, define a maximum scroll count or item count so the job cannot run indefinitely.
Cookie banners or chat widgets cover the result
Cause: those elements are part of the rendered page state. Dismiss them through their UI when appropriate, or hide narrowly targeted selectors immediately before capture. Broad rules such as hiding every fixed element can remove legitimate navigation.
Recommended Free Tools
Best Value
- Used Book in Good Condition
Sticky headers appear several times
Cause: a scrolling or stitching workflow captured a viewport-relative element in multiple segments. Hide or reset the sticky element during the capture, or use native full-page capture when it produces the correct result. Inspect pages with fixed navigation rather than assuming all sites behave alike.
Later files overwrite earlier files
Cause: every iteration uses the same path. Include an index or sanitized hostname, and create the directory once before the loop.
Chrome fails to launch in CI
Cause: missing browser dependencies, sandbox restrictions or an unavailable executable. Install the required system packages, use the browser downloaded by Puppeteer, or pass a verified executablePath. Keep launch errors separate from page errors so a browser-level failure is not misdiagnosed as a bad URL.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP or PDF, so you do not need to manage Chrome, page lifecycles or screenshot files yourself. Before capture it accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP tools—take_screenshot, get_page_info and capture_pdf—let Claude, Cursor or another MCP client request captures.
Use the documented API details at https://screenshotneo.com/docs/. The same call can be adapted for each URL in your list:
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 supports full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets or any viewport, retina scale, PDF paper and page-range controls, custom CSS and JavaScript, pre-capture clicks, selector hiding, selector/delay/network-idle waits, blocking ads/trackers/requests/resource types, custom headers/cookies/user agents/Authorization, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed public image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.
There is a free allowance of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is available on every plan. Start with a free ScreenshotNeo account.
Practical checklist before a batch run
- Confirm every URL and decide how redirects, authentication and failures should be handled.
- Set one viewport and device scale factor before navigation.
- Choose
networkidle2, a selector, an application-ready signal or a justified delay for each page type. - Trigger lazy loading and wait for important fonts or images.
- Use unique, deterministic filenames and create the output directory.
- Inspect a tall page, a page with sticky UI and a page with consent controls.
- Cap concurrency and monitor memory when processing many URLs.
- Record errors and continue only when a partial batch is acceptable.
Frequently Asked Questions
Can one Puppeteer browser handle many websites?
Yes. Create one browser with puppeteer.launch(), create and close a Page for each URL, then close the browser after the loop.
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 →What does networkidle2 guarantee?
It waits for no more than two active network connections; it does not prove that delayed application rendering, lazy content or animations are complete.
Should I use a stitching package for every page?
No. Native fullPage: true is the normal first choice. A stitching package is useful to investigate unusually tall pages or viewport-relative layout artifacts, but it can duplicate sticky elements.
How do I capture a PDF instead of an image?
Use Puppeteer’s PDF workflow when you need a document rather than an image, or use ScreenshotNeo’s capture_pdf MCP tool/API options for paper size, margins, orientation and page ranges.
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.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.




