ScreenshotNeo

BlogGuides

Best Puppeteer Screenshot Options for Consistent Visual Tests

Make Puppeteer screenshots repeatable by controlling capture scope, viewport, page state, assets, and readiness before comparing results.

By the ScreenshotNeo team4 October 20267 min read

For consistent Puppeteer visual tests, choose the same capture scope every run and hold the browser version, viewport, device scale factor, page data, fonts, assets, and interface state steady. Wait for the specific application state you intend to test, then capture with page.screenshot() or elementHandle.screenshot(). Puppeteer captures images; it does not itself compare them with a visual baseline.

The most useful screenshot settings depend on what the test checks: viewport screenshots for the visible screen, fullPage screenshots for the whole scrollable page, and element screenshots for an individual component. Confirm the options supported by your installed Puppeteer version in the Page.screenshot API reference and screenshots guide.

1. Choose the capture scope that matches the test

Capture Use it for Things to keep consistent
Viewport The visible screen at a defined scroll position Viewport dimensions, device scale factor, and scroll position
Full page Layout across the full scrollable page, including below-the-fold content Use the same full-page setting for baseline and later captures; long pages may load content as they scroll
Element A component or region, such as a card or navigation bar Selector, element state, and any scrolling that occurs to bring it into view

Do not compare a viewport baseline with a full-page capture or change the selected element between runs. An element screenshot may scroll a hidden element into view. That can affect sticky elements or trigger lazy-loaded content, so account for the resulting page state when designing the test.

2. Build a stable capture before comparing it

  1. Pin the Puppeteer and browser versions used in the test environment. Browser rendering can change across runtime updates.
  2. Set the same viewport and device scale factor before navigation. Keep the operating system and available fonts consistent where possible.
  3. Use deterministic test data and a known application state. Avoid changing timestamps, randomized content, rotating banners, or user-specific data unless the test is meant to cover them.
  4. Wait for an application-specific readiness condition, such as a test marker or a component becoming visible. A delay or network-idle condition alone does not prove that a dynamic interface has finished rendering.
  5. Control animations and other changing visual states in the application or test setup when they are irrelevant to the assertion.
  6. Use the same screenshot options, output format, and capture scope for the baseline and every subsequent run.

Puppeteer’s guide demonstrates navigation with networkidle2, but pages with polling, streaming, analytics, or other ongoing requests may not settle predictably. Prefer a condition tied to the interface state your test actually needs.

3. Runnable Puppeteer example

This CommonJS example fixes the viewport and pixel scale, waits for a page-specific readiness marker, and saves a viewport screenshot. Replace the URL and selector with values from your application.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage({
      viewport: { width: 1440, height: 900 },
      deviceScaleFactor: 1,
    });

    await page.goto('http://localhost:3000', { waitUntil: 'domcontentloaded' });
    await page.waitForSelector('[data-test="visual-ready"]', { visible: true });
    await page.screenshot({ path: 'page.png', type: 'png' });
  } finally {
    await browser.close();
  }
})();

The readiness selector is an example contract between your app and test, not a built-in Puppeteer feature. Add a marker only when the UI has reached the state the test is meant to capture. Keep launch configuration and browser build fixed in CI and local baseline generation.

Full-page capture

When the test is about the entire page’s vertical layout, use the same full-page option on every run:

await page.screenshot({ path: 'full-page.png', fullPage: true });

Check the installed-version API reference for exact option support and behavior. Full-page capture can expose content that is not visible in an ordinary viewport screenshot; lazy-loaded sections may require deliberate scrolling or application-level readiness before capture.

Element capture

Use a stable selector and wait for the component to be visible before capturing it:

const card = await page.waitForSelector('[data-test="summary-card"]', { visible: true });
if (!card) throw new Error('Summary card was not found');
await card.screenshot({ path: 'summary-card.png' });

Choose selectors intended for testing, such as dedicated data-test attributes, instead of fragile selectors based on incidental markup. Consider whether scrolling the element into view changes sticky UI or triggers lazy loading.

4. Keep screenshot capture separate from visual comparison

page.screenshot() and elementHandle.screenshot() produce captures. A separate test assertion or image comparison step must decide whether a new capture matches a baseline. Store the baseline and comparison output with enough context to identify the browser version, viewport, test data, and screenshot scope that produced them.

If you want comparison integrated into a test framework, Playwright Test documents screenshot assertions that compare against baselines and retry until consecutive screenshots match. Those are Playwright Test capabilities, not Puppeteer screenshot options. See the Playwright visual comparisons documentation. For centrally reviewed snapshots, Percy documents a Playwright integration with named snapshots, full-page capture, and dynamic-region controls; check compatibility and current terms before adopting it. See Percy’s Playwright integration guide.

5. Screenshot options and configuration checklist

  • Scope: viewport, full page, or a selected element. Match the baseline’s scope exactly.
  • Viewport and scale: set dimensions and device scale factor before navigation, and keep them fixed.
  • Readiness: wait for the application state relevant to the assertion; do not assume elapsed time means the page is visually settled.
  • Page state: make data, authentication state, selected tabs, expanded controls, and scroll position deterministic.
  • Assets: ensure fonts, images, and stylesheets load consistently. Missing or late resources can change layout and pixels.
  • Version support: use the API reference matching the Puppeteer version in your project; avoid copying options from a different version or another framework.
  • Comparison: choose and configure a distinct baseline comparison step. Puppeteer capture alone does not report visual diffs.

The dossier’s official references establish page and element capture and the full-page option. They do not supply a universal list of supported screenshot parameters across versions, so consult the installed-version API reference before relying on other options.

6. Troubleshooting inconsistent screenshots

Symptom Likely cause Fix
Text wraps differently between runs Viewport, device scale, font availability, or font loading changed Fix viewport and scale; make fonts available and wait for the app’s font-ready state before capture.
Some images or sections are missing Capture began before resources loaded, or content is lazy-loaded Wait for the relevant application marker or resource condition; scroll deliberately if the test depends on lazy content.
Capture hangs waiting for network idle The page has polling or persistent requests Use a selector or application readiness condition instead of treating network idle as a universal signal.
Sticky header differs in element capture Capturing an off-screen element scrolled it into view Set up the intended scroll position and state explicitly, or capture a region whose behavior matches the test.
Full-page baseline has a different height Content, viewport width, loaded fonts, or page state changed Fix data and viewport, verify readiness, and use full-page capture consistently for both baseline and new run.
Screenshot succeeds but no visual failure is reported Capture was mistaken for comparison Add a baseline comparison/assertion step; Puppeteer’s screenshot method only captures.
An option is rejected or ignored Example uses an option unsupported by the installed version or the wrong method Check the matching Puppeteer API reference for the exact method and version.

7. Performance, reliability, and cost

Screenshot runtime includes browser startup, navigation, resource loading, readiness waits, and image encoding. Reuse a browser process across related captures where your test harness safely supports it, while isolating page state between cases. Keep waits specific: an unnecessarily broad wait slows a suite, while a premature capture makes it unreliable.

For reliable CI, pin runtime dependencies, keep test data stable, record the capture settings with the baseline, and make failures retain both the new image and comparison result. Puppeteer’s local capture has no per-screenshot service charge, but it consumes CI time and compute; hosted visual review services may have their own current pricing and compatibility constraints, which should be checked with the provider.

8. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. Use it when you need a hosted capture rather than maintaining browser setup. Its API can return PNG, JPEG, WebP, or PDF, and its parameters include full-page capture, element selection, viewport and device presets, custom CSS and JavaScript, waits, cookies and headers, and caching. See the ScreenshotNeo API documentation for request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie banners are accepted and removed, along with known consent platforms, newsletter popups, and chat widgets, before capture; each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers report the page verdict and billing status.
  • An MCP server gives AI agents tools for screenshots, page information, and PDF capture.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.

Sign up for 1,000 free screenshots a month, with no card required.

9. FAQ

Does Puppeteer compare screenshots with a baseline?

No. Puppeteer provides capture methods; add a separate comparison or assertion step.

Should I always use full-page capture for visual tests?

No. Use it when the behavior under test spans the full scrollable page. Otherwise choose the viewport or a component capture that matches the assertion.

Is a fixed delay enough to make a screenshot stable?

Not reliably. A condition tied to the application state gives the test a clearer signal than waiting an arbitrary duration.