Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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, inspect page.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() in finally.

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.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.