Use Puppeteer’s page.screenshot() method to capture a browser page. Navigate to the URL, wait for a condition that means the page is ready, then choose a viewport, full-page capture, clip rectangle, element handle, output format, and delivery method (file, bytes, or Base64). Puppeteer drives Chrome and Firefox through the Chrome DevTools Protocol and WebDriver BiDi, so the same script can produce repeatable screenshots for tests, documentation, previews, and automation.
Install Puppeteer and create a browser
Start a Node.js project and install Puppeteer. The package downloads a compatible browser during installation unless you are using a separately managed Chrome or Chromium binary.
mkdir puppeteer-shots
cd puppeteer-shots
npm init -y
npm install puppeteer
Create a script that launches headless Chrome, opens a page, and closes the browser in a finally block. Closing the browser matters in CI and server processes because an orphaned browser can keep the job alive and consume memory.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({headless: true});
try {
const page = await browser.newPage();
await page.setViewport({width: 1440, height: 900, deviceScaleFactor: 1});
await page.goto('https://example.com', {waitUntil: 'networkidle2'});
await page.screenshot({path: 'page.png'});
} finally {
await browser.close();
}
})();
networkidle2 waits until there are no more than two network connections for the relevant period. It is a useful default for mostly static pages, but it is not a universal definition of “ready.” Analytics, live feeds, WebSockets, advertisements, and polling can keep traffic active. For those pages, wait for an application-specific selector or readiness flag instead.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →#1 Best Overall
Capture a viewport screenshot
With no special options, page.screenshot() captures the visible viewport as a PNG. The path option writes the result relative to the process’s current working directory when you provide a relative path.
await page.screenshot({path: 'artifacts/home.png'});
Set the viewport before navigation when responsive layout matters. The viewport width, height, device scale factor, color scheme, and user agent can all change what the page renders.
await page.setViewport({
width: 390,
height: 844,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
});
await page.goto('https://example.com', {waitUntil: 'domcontentloaded'});
await page.screenshot({path: 'phone.png'});
A high deviceScaleFactor creates a denser image; it does not change CSS viewport dimensions. Keep the factor consistent when comparing screenshots in visual tests.
Capture the entire scrollable page
Pass fullPage: true to extend the capture to the page’s full scrollable height instead of only the viewport.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →await page.goto('https://example.com/docs', {waitUntil: 'networkidle2'});
await page.screenshot({path: 'docs-full.png', fullPage: true});
“Full page” describes the capture extent, not a guarantee that every lazy image, font, or client-rendered section has loaded. Make readiness explicit before taking the shot.
Rank #2
Wait for lazy content
await page.goto('https://example.com/gallery', {waitUntil: 'domcontentloaded'});
await page.evaluate(async () => {
const step = 600;
for (let y = 0; y < document.body.scrollHeight; y += step) {
window.scrollTo(0, y);
await new Promise(resolve => setTimeout(resolve, 100));
}
window.scrollTo(0, 0);
});
await page.waitForNetworkIdle({concurrency: 2, idleTime: 500});
await page.screenshot({path: 'gallery.png', fullPage: true});
Scrolling triggers many lazy-loading implementations. If your application exposes a stronger signal, prefer it:
await page.waitForSelector('[data-page-ready="true"]', {timeout: 30000});
await page.screenshot({path: 'ready.png', fullPage: true});
Screenshot one element
For a card, chart, logo, or other DOM node, obtain an element handle and call elementHandle.screenshot(). This automatically uses the element’s rendered bounding box.
const card = await page.waitForSelector('.pricing-card');
if (!card) throw new Error('pricing card was not found');
await card.screenshot({path: 'pricing-card.png'});
Make the element visible and stable first. A hidden node, a zero-size node, or an element that is still animating can produce an error or an unexpected image.
Free tools Windows power users keep installed
One-click scans. No signup required.
await page.waitForSelector('.chart canvas');
await page.evaluate(() => document.querySelector('.chart').scrollIntoView({block: 'center'}));
await new Promise(resolve => setTimeout(resolve, 300));
const chart = await page.$('.chart');
await chart.screenshot({path: 'chart.png'});
Clip a precise rectangle
Use clip when you need coordinates rather than a DOM node. The rectangle has x, y, width, and height values in CSS pixels.
await page.screenshot({
path: 'hero-crop.png',
clip: {x: 80, y: 120, width: 900, height: 500}
});
Coordinates are relative to the page and can be affected by scrolling and device scale. For a responsive design, an element screenshot is usually less fragile than hard-coded coordinates. captureBeyondViewport controls whether Puppeteer captures content outside the current viewport; its default depends on whether a clip is supplied, so set it explicitly when the boundary matters.
Choose PNG, JPEG, WebP, transparency, and return mode
The documented default image type is PNG. Set type to jpeg or webp when your downstream system supports those formats. JPEG and WebP accept a quality value from 0 to 100; quality does not apply to PNG.
await page.screenshot({path: 'photo.jpg', type: 'jpeg', quality: 82});
await page.screenshot({path: 'preview.webp', type: 'webp', quality: 80});
Use omitBackground: true to remove the default white page background and preserve transparent areas where the browser can render them.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteawait page.screenshot({path: 'logo.png', omitBackground: true});
Omit path when another program should receive the image directly. Puppeteer returns binary Uint8Array data by default, or a Base64 string with encoding: 'base64'.
const bytes = await page.screenshot({type: 'png'});
require('fs').writeFileSync('from-bytes.png', bytes);
const base64 = await page.screenshot({encoding: 'base64'});
console.log(`data:image/png;base64,${base64}`);
Control what the page renders
Wait for a selector, delay, or application signal
await page.goto(url, {waitUntil: 'domcontentloaded'});
await page.waitForSelector('#report-complete', {timeout: 30000});
await new Promise(resolve => setTimeout(resolve, 500));
A selector wait is generally more meaningful than an arbitrary delay. A short delay is still useful for animations, web fonts, or a chart that draws after its data arrives.
Freeze motion and hide unwanted UI
await page.addStyleTag({content: `
*, *::before, *::after {
animation: none !important;
transition: none !important;
caret-color: transparent !important;
}
`});
await page.addStyleTag({content: `
.cookie-banner, .newsletter-modal, .live-chat {display: none !important;}
`});
Only hide elements when that reflects the screenshot you intend to publish or test. Removing a consent dialog with CSS is not the same as interacting with it; pages may not reveal content until consent is actually accepted.
Rank #4
Set media, headers, cookies, and authentication
await page.setExtraHTTPHeaders({'Authorization': `Bearer ${process.env.TOKEN}`});
await page.setCookie({name: 'session', value: process.env.SESSION, domain: 'example.com'});
await page.emulateMediaType('screen');
await page.goto('https://example.com/private', {waitUntil: 'networkidle2'});
Use an isolated browser context for separate users or test cases. Do not place long-lived secrets in source code or screenshot filenames.
Recommended Free Tools
Interact before capturing
await page.click('[data-tab="analytics"]');
await page.waitForSelector('#analytics-panel:not([hidden])');
await page.screenshot({path: 'analytics-tab.png'});
Screenshot versus PDF
Use page.screenshot() for raster images. Use page.pdf() for a PDF deliverable; PDF generation follows print CSS media by default.
await page.emulateMediaType('screen');
await page.pdf({
path: 'report.pdf',
format: 'A4',
printBackground: true,
margin: {top: '12mm', right: '12mm', bottom: '12mm', left: '12mm'}
});
Screen media is useful when the site’s print stylesheet hides or rearranges content. If exact print colors matter, apply -webkit-print-color-adjust: exact in the page’s print styling. A PDF is paginated and selectable; a screenshot is a fixed raster image.
Build a reusable capture script
This complete example accepts a URL and output path, waits for a readiness selector when supplied, scrolls to trigger lazy content, and captures a full-page WebP.
const puppeteer = require('puppeteer');
const fs = require('node:fs/promises');
const [, , url, output = 'shot.webp', readySelector] = process.argv;
if (!url) throw new Error('Usage: node shot.js URL [output] [readySelector]');
(async () => {
const browser = await puppeteer.launch({headless: true});
try {
const page = await browser.newPage();
await page.setViewport({width: 1440, height: 900, deviceScaleFactor: 1});
await page.goto(url, {waitUntil: 'domcontentloaded', timeout: 60000});
if (readySelector) await page.waitForSelector(readySelector, {timeout: 30000});
await page.evaluate(async () => {
for (let y = 0; y < document.body.scrollHeight; y += 700) {
window.scrollTo(0, y);
await new Promise(r => setTimeout(r, 80));
}
window.scrollTo(0, 0);
});
await page.screenshot({path: output, fullPage: true, type: 'webp', quality: 85});
console.log(`Saved ${output}`);
} finally {
await browser.close();
}
})();
Run it like this:
node shot.js https://example.com artifacts/example.webp '[data-page-ready="true"]'
Troubleshooting Puppeteer screenshots
The screenshot is blank or navigation times out
- Cause: the page failed, requires authentication, blocks the browser, or never reaches the selected wait condition.
- Fix: try
domcontentloaded, inspectpage.url()and console errors, increase the navigation timeout, and verify the URL and credentials. Do not treat a timeout as a successful capture.
Content below the fold is missing
- Cause: full-page geometry was captured before lazy content loaded.
- Fix: scroll through the document, wait for the relevant images or readiness selector, then call
fullPage: true.
A cookie banner or chat widget covers the page
- Cause: the overlay is part of the rendered DOM.
- Fix: interact with its accept/close control, set the appropriate cookie, or hide a known selector only when that is valid for your use case.
An element screenshot fails
- Cause: the selector matched nothing, the node has no layout box, or it is detached during a rerender.
- Fix: wait for the selector, check the handle for
null, disable the animation, and reacquire the handle immediately before capture.
Images differ between runs
- Cause: fonts, animations, time-dependent data, viewport differences, or nondeterministic ads.
- Fix: use a fixed viewport and device scale, wait for fonts and app readiness, freeze animation, and block or mock unstable resources where your test permits it.
The process hangs after saving
- Cause: the browser or a page remains open.
- Fix: close pages and contexts, and always call
browser.close()infinally.
Performance, reliability, and cost considerations
- Reuse one browser process for a batch, but create separate pages or browser contexts for isolation.
- Use a targeted selector or clip instead of a full-page image when the consumer needs only one component.
- Wait for the page’s real readiness signal rather than an unnecessarily long fixed delay.
- Limit concurrent pages to what the host can support; more parallel tabs increase CPU and memory pressure.
- Store binary output directly when possible. Base64 is convenient for JSON transport but increases payload size.
- Record the URL, viewport, wait condition, browser version, and capture timestamp with visual-test artifacts so a mismatch can be reproduced.
- Puppeteer itself does not provide a screenshot price or quota. Your costs come from the machine, browser runtime, storage, and any external service you add.
Or skip the browser setup: ScreenshotNeo
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF, so you do not have to install Puppeteer or manage a browser for a straightforward URL capture. Before the capture it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks and 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.
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 matchPC 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 & 11For developers, it also supports full-page and selector captures, device presets or custom viewports, retina scale, dark mode, custom CSS and JavaScript, clicks, waits, blocked resources, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
Use the API key from your account. The same endpoint accepts common screenshot-API parameter names, which can simplify migration.
cURL
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)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
require('node:fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));
See the ScreenshotNeo documentation for option names and response handling. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to begin.
FAQ
Can Puppeteer screenshot a page without saving a file?
Yes. Omit path and use the returned Uint8Array, or request a Base64 string with encoding: 'base64'.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Should I use a screenshot or a PDF for a report?
Choose a screenshot for a fixed raster image and a PDF for paginated, print-oriented output. PDF generation uses print media unless you select screen media.
What is the safest wait condition?
Use the application’s own readiness selector or state when available. Network idle is only a proxy and may never occur on pages with continuous traffic.
Frequently Asked Questions
Can Puppeteer screenshot a page without saving a file?
Yes. Omit path and use the returned binary data, or request Base64 with encoding: 'base64'.
Does fullPage guarantee that lazy images are present?
No. It expands the capture area; scroll or wait for the page’s own readiness signal before capturing lazy-loaded content.
When should I use an element screenshot instead of clip?
Use an element handle when the target is a DOM node that moves responsively. Use clip for a fixed coordinate rectangle.
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.

