Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
The reliable way to capture a web page in code is to render it in a real browser, wait for the state you need, and call that browser’s screenshot API. With Playwright, the basic operation is await page.screenshot({ path: 'screenshot.png' }); add fullPage: true for the complete scrollable document. Puppeteer provides equivalent page and element methods, while Chrome DevTools Protocol (CDP) exposes the lower-level Page.captureScreenshot command.
Choose the capture level first
Decide what the output must contain before writing code. A viewport screenshot is exactly what a user currently sees. A full-page screenshot extends through the document’s scrollable height. An element screenshot crops to one component such as a chart, invoice, or article body. These are different operations, not interchangeable settings.
- Viewport: useful for responsive-regression tests and device previews.
- Full page: useful for archives, visual reviews, and bug reports that need content below the fold.
- Element: useful when surrounding navigation or ads should not be included.
- Protocol capture: appropriate when you need direct CDP control over clipping and encoding rather than a higher-level library.
Playwright: the most complete high-level workflow
Playwright’s page API handles navigation, waiting, full-page capture, element targeting, and masking. Install the Playwright package and the browser binaries for your project, then run a script like this (use the language binding and version installed in your environment).
Minimal PNG capture
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 1
});
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'screenshot.png' });
await browser.close();
waitUntil: 'networkidle' is convenient for mostly static pages, but it is not a guarantee that every image or animation is finished. For deterministic tests, wait for a meaningful selector and, where necessary, disable animation with injected CSS.
#1 Best Overall
Full-page capture
await page.goto('https://example.com/article', { waitUntil: 'domcontentloaded' });
await page.locator('main').waitFor();
await page.screenshot({
path: 'article-full.png',
fullPage: true,
type: 'png'
});
fullPage: true captures the full scrollable page rather than only the current viewport. Pages that lazy-load content while scrolling may need an explicit scroll routine before the screenshot so those resources are requested.
Capture one element
const card = page.locator('[data-testid="invoice-card"]');
await card.waitFor({ state: 'visible' });
await card.screenshot({ path: 'invoice-card.png', type: 'png' });
Element screenshots normally include the element’s bounding box. Ensure the selector identifies one stable element; a selector matching several nodes can produce an error or an unintended target.
Mask dynamic or private regions
await page.screenshot({
path: 'masked.png',
fullPage: true,
mask: [page.locator('.user-email'), page.locator('.live-counter')],
maskColor: '#777777'
});
Masking is useful when a visual test should ignore personal data or rapidly changing values. Prefer masking to editing the file afterward because the browser applies it during capture.
Free tools Windows power users keep installed
One-click scans. No signup required.
Useful Playwright options
type: 'png' | 'jpeg'selects the image format; JPEG also accepts a quality value.pathwrites a file; omit it to receive a buffer for storage or further processing.omitBackground: truecan preserve transparency where the page has no opaque background.animations: 'disabled'(where supported by your installed version) helps stabilize motion.- Set the context’s viewport, device scale factor, color scheme, locale, timezone, and user agent before navigation to make the rendering reproducible.
Check the Playwright API and screenshots documentation for the exact option names supported by your installed binding; these details are version-sensitive.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
Make the rendered state deterministic
Wait for content, not an arbitrary delay
await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.locator('#dashboard-ready').waitFor({ state: 'visible' });
await page.waitForLoadState('networkidle');
A selector-based wait expresses the condition you actually need. A fixed timeout can still be too short on a slow run and wastes time on a fast run. Use a short delay only for a known transition that has no observable selector.
Load lazy images
await page.evaluate(async () => {
window.scrollTo(0, document.body.scrollHeight);
await new Promise(resolve => setTimeout(resolve, 300));
window.scrollTo(0, 0);
});
await page.screenshot({ path: 'lazy-loaded.png', fullPage: true });
For production, replace the delay with checks that expected images have completed, and consider waiting for each image’s complete property. Infinite-scroll pages require a defined stopping rule; otherwise “full page” has no finite endpoint.
Control fonts, animation, and privacy
await page.addStyleTag({ content: `
*, *::before, *::after {
animation: none !important;
transition: none !important;
caret-color: transparent !important;
}
` });
await page.screenshot({ path: 'stable.png' });
Use a dedicated test account, redact secrets in the page itself, and never place access tokens in a URL that could be saved in logs. Set cookies and headers through the browser context when authentication is required.
Puppeteer: JavaScript capture with Chrome or Firefox
Puppeteer’s guide documents Page.screenshot() for a page and ElementHandle.screenshot() for a specific element. The guide result referenced version 25.12.0; verify the options against the version installed in your project.
Rank #3
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
await page.screenshot({ path: 'puppeteer-full.png', fullPage: true });
const heading = await page.$('h1');
if (heading) await heading.screenshot({ path: 'heading.png' });
await browser.close();
Puppeteer is a higher-level automation library over browser protocols. Its API is straightforward for JavaScript teams, but browser launch flags, bundled-browser behavior, and option names can vary by release.
Chrome DevTools Protocol: direct control
CDP’s Page domain provides Page.captureScreenshot. This is lower level: you must manage a browser connection, enable the Page domain, and decide how to represent a clip rectangle and image format.
// Conceptual CDP sequence
await cdp.send('Page.enable');
const result = await cdp.send('Page.captureScreenshot', {
format: 'png',
fromSurface: true,
captureBeyondViewport: true,
clip: { x: 0, y: 0, width: 1200, height: 800, scale: 1 }
});
// result.data is base64-encoded image data
The exact connection code depends on whether you attach to an existing Chrome instance, use a WebSocket endpoint, or let an automation library create the session. CDP’s “tot” documentation evolves with Chrome, so verify command parameters against the browser version you automate. Use CDP when protocol-level clipping or integration with an existing Chrome process matters more than convenience.
Format, size, and repeatability decisions
| Requirement | Recommended choice | Reason |
|---|---|---|
| Pixel-perfect comparison | PNG, fixed viewport and device scale | Lossless output avoids JPEG artifacts. |
| Small image for a report | JPEG with an explicit quality | Smaller files, with some compression loss. |
| Print or document workflow | Capture at the intended CSS dimensions, then convert separately | Browser screenshots are raster images; print pagination is a separate concern. |
| Responsive coverage | Run the same URL at several viewport widths | One viewport cannot expose layout breakpoints. |
Keep browser version, fonts, locale, timezone, color scheme, and network fixtures consistent when comparing runs. A changed font or missing webfont can move every element even when application code is unchanged.
Rank #4
Troubleshooting common failures
The image is blank or incomplete
Check the navigation result and console errors, wait for a visible application-ready selector, and confirm that the page is not behind authentication or a bot challenge. Capture after the relevant content exists, not immediately after goto.
Full-page output stops early
Verify that the document actually has the expected scroll height. Scroll to trigger lazy loading, wait for newly requested images, and avoid an infinite-scroll endpoint without a maximum number of batches.
Fonts or icons differ between runs
Wait for document.fonts.ready, install the same fonts in every runner, and keep browser and operating-system versions consistent.
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 problemsElement capture throws a visibility or timeout error
Confirm the selector, wait for visible, scroll the element into view, and check whether an iframe contains the target. If it is inside an iframe, obtain the frame and locate the element there.
Best Value
Page navigation times out
Inspect DNS, TLS, proxy, and authentication first. Increase the timeout only after identifying a slow dependency; otherwise a longer timeout hides a genuine failure. Record the URL, HTTP status where available, and browser logs for diagnosis.
Only part of a fixed or sticky layout appears
That may be correct for a viewport capture. Use full-page mode for document content, or capture a specific element. Fixed headers can repeat or overlap in long captures; mask or hide them with test-only CSS if your comparison requires a clean document.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. A single request returns PNG, JPEG, WebP, or PDF output:
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 matchcurl -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}`);
See the ScreenshotNeo documentation for parameters. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Options include full-page and CSS-selector captures, dark mode, device presets, retina scale, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification.
The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.
Which approach should you use?
- Use Playwright when you need cross-browser automation, rich waits, masking, and repeatable test contexts.
- Use Puppeteer when your JavaScript project is already centered on its API and Chrome automation.
- Use CDP when you need direct protocol control or must attach to an existing Chrome session.
- Use ScreenshotNeo when maintaining browsers, consent cleanup, asynchronous jobs, or AI-agent access is more work than the capture itself.
Frequently Asked Questions
Can a screenshot script capture a page that requires login?
Yes. Create an authenticated browser context, sign in through the normal flow, or provide the required cookies and headers before navigating. Keep credentials out of source control and screenshot filenames.
Why is a full-page screenshot different from a PDF?
A full-page screenshot is one raster image of the rendered document. A PDF uses pagination and paper settings, so it can split content across pages and preserve a document-oriented layout.
Recommended Free Tools
How do I capture a region that is not a DOM element?
Use a clipping rectangle in the browser API or CDP. Element capture is preferable when a stable selector exists; coordinate clips require a fixed viewport and are more sensitive to layout changes.
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.

