ScreenshotNeo

BlogComparisons

Puppeteer vs Playwright Screenshot Quality and Full-Page Capture Differences

Puppeteer and Playwright both capture full pages, but their options differ. Learn how to compare output fairly, write runnable scripts, and diagnose visual differences.

By the ScreenshotNeo team4 October 202610 min read

Puppeteer and Playwright both support full-page screenshots. Their documentation does not establish that either library always produces higher-quality images, or that their outputs are pixel-identical. For a fair comparison, hold the browser engine, versions, viewport, device scale, page state, loaded fonts and assets, and animation state constant. Then inspect dimensions, clipping, sticky elements, lazy-loaded content, and visual artifacts on the pages your project actually uses.

The practical difference is in the controls each API documents. Playwright exposes CSS-pixel versus device-pixel output, animation handling, and locator masks. Puppeteer exposes captureBeyondViewport, whose default depends on whether a clip is provided. Those options can affect what you see and how large the image is; they do not make one tool a universal quality winner.

What “screenshot quality” means

Separate capture scope, rendering fidelity, and output resolution. A tall image is not automatically more faithful, and a larger image is not necessarily sharper in a useful way.

  • Scope: visible viewport, a clipped region, an element, or the full scrollable page.
  • Rendering fidelity: whether the page has reached the intended state and whether fonts, images, animations, and dynamic content are present.
  • Pixel scale and format: CSS pixels versus device pixels, plus PNG or JPEG and its quality setting where supported.

Both products document full-page capture. Playwright describes it as capturing the full scrollable page as if it had a very tall viewport. Puppeteer documents fullPage in its screenshot options. Similar options do not guarantee identical implementation or pixels. See the Playwright screenshots guide and Puppeteer ScreenshotOptions reference.

Documented differences that affect captures

Concern Puppeteer Playwright What to do
Full page fullPage captures the full page. fullPage: true captures the full scrollable page. Check long pages and pages with sticky or lazy content in your target engine.
Clipping clip selects a region. captureBeyondViewport controls capture beyond the viewport; its default is false without a clip and true with one. clip selects a region; fullPage controls full-page capture. Set intended capture extent explicitly and avoid combining modes without checking the API version.
Pixel scale The cited screenshot options document type and quality, but not a scale control. scale: 'css' emits one pixel per CSS pixel; 'device' uses device pixels. Record scale and viewport when comparing dimensions and file sizes.
Animation and caret These are not listed in the cited ScreenshotOptions fields. Screenshot options can disable or allow animations and hide or retain the caret. Use a consistent policy for repeatable visual checks.
Dynamic regions The cited options do not list locator masking. Locator masks can cover selected elements during capture. Mask volatile content if the purpose is regression comparison, and document the mask.
Output Screenshot options include type and quality; quality does not apply to PNG. Supports type, quality, transparency, path, and buffer output. Compare like-for-like formats and quality settings.
Engines The cited pages do not establish a cross-engine comparison. Playwright documents Chromium, Firefox, and WebKit support. Use the engine or engines that matter to your deployment.

References: Puppeteer screenshot options, Puppeteer screenshots guide, Playwright screenshots guide, and the Playwright Page API. APIs can change; check the documentation matching the installed version.

How to compare screenshot output fairly

  1. Choose the same engine. Do not compare Puppeteer Chromium output to Playwright WebKit output and attribute every difference to the library.
  2. Pin browser and library versions. Record them with the result so later upgrades are not mistaken for tool differences.
  3. Set the same viewport and scale. Use identical width and height, and choose an intentional device scale. Playwright can explicitly choose CSS or device scale; Puppeteer’s documented screenshot options do not expose the same scale field.
  4. Use the same URL and page state. Start from the same route, cookies, storage, authentication, and interaction sequence.
  5. Wait for the same readiness condition. Wait for a stable selector or application state, and ensure fonts and important images have loaded. A navigation event alone may not mean every visual asset is ready.
  6. Set animation behavior. Playwright offers animation control; for Puppeteer, arrange a stable page state in the page itself if animation affects the result.
  7. Capture with matching scope and format. Use full-page in both, or matching clips in both. Compare PNG with PNG, or JPEG at equivalent quality settings.
  8. Inspect both pixels and dimensions. Check image width and height, crop edges, content at the bottom, sticky headers, repeated sections, and lazy-loaded images. Treat the procedure as a controlled comparison, not a benchmark published by either project.

For repeatable visual tests, Playwright also provides screenshot assertions with screenshot options; see the PageAssertions API. The assertion workflow does not remove the need to control page state.

Runnable Puppeteer example

Install Puppeteer with npm install puppeteer. This CommonJS script opens a page, waits for a content selector, then writes a full-page PNG:

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('https://example.com', { waitUntil: 'networkidle0' });
    await page.waitForSelector('h1');
    await page.screenshot({ path: 'puppeteer-full.png', fullPage: true, type: 'png' });
  } finally {
    await browser.close();
  }
})();

networkidle0 can be unsuitable for pages that keep connections open or poll continuously. In that case, use a navigation condition appropriate to the page, then wait for a reliable application selector or other readiness signal. The Puppeteer screenshots guide also documents element screenshots; element capture scrolls the element into view when needed.

Runnable Playwright example

Install Playwright and its browser with npm install playwright and npx playwright install chromium. This script uses Chromium, a fixed viewport, CSS-pixel scale, disabled animations, and a full-page PNG:

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ headless: true });
  try {
    const page = await browser.newPage({
      viewport: { width: 1440, height: 900 },
      deviceScaleFactor: 1,
    });
    await page.goto('https://example.com', { waitUntil: 'networkidle' });
    await page.locator('h1').waitFor();
    await page.screenshot({
      path: 'playwright-full.png',
      fullPage: true,
      type: 'png',
      scale: 'css',
      animations: 'disabled',
      caret: 'hide',
    });
  } finally {
    await browser.close();
  }
})();

For another documented option, mask a changing region by locator when capturing. Prefer a stable selector and remember the mask changes the image being evaluated:

await page.screenshot({
  path: 'masked.png',
  fullPage: true,
  mask: [page.locator('[data-testid="live-clock"]')],
});

cURL, Python, and Node.js with ScreenshotNeo

For a managed screenshot instead of running a browser, ScreenshotNeo accepts a URL in one GET request and returns an image or PDF. These examples request a WebP screenshot; create an API key in your account and replace the placeholder. See the ScreenshotNeo API documentation for request options.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.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://example.com',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));

The Node.js snippet uses Bun’s file-writing helper. With Node.js alone, save the response body using node:fs/promises:

const { writeFile } = require('node:fs/promises');
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. Cookie banners are accepted like a visitor and 60+ known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -o shot.webp

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

Full-page capture, elements, and clipping

Use full-page capture when the deliverable should contain the whole scrollable document. Use an element screenshot when you need a component or card, and a clip when you need a fixed region. Puppeteer documents element capture in its screenshots guide; Playwright exposes clipping and full-page settings through page.screenshot().

Full-page output deserves extra attention on pages with:

  • Lazy-loaded images: they may not load until scrolled near the viewport. A full-page option does not itself guarantee that every application-specific lazy asset has loaded. Trigger the page’s normal loading behavior or wait for the relevant images before capture.
  • Sticky and fixed elements: long-page capture can expose repeated or differently positioned behavior. Inspect headers, floating buttons, and fixed overlays in the output.
  • Very long documents: image dimensions and memory requirements grow with page height. Capture only the needed area if a full-page image is too large.
  • Changing content: timestamps, ads, carousels, and live data can make two captures differ even when the browser setup is identical. Freeze or mask such regions when suitable.

Why does my full-page screenshot look different?

Symptom Likely cause Fix
Different dimensions Viewport, device scale, full-page extent, or clipping differs. Record viewport and scale, remove accidental clips, and compare output dimensions first.
Text wraps differently Viewport width, font readiness, browser engine, or loaded web fonts differ. Match width and engine; wait for fonts and critical assets before capture.
Images are blank or missing near the bottom Lazy loading has not been triggered, or the image request failed. Scroll or otherwise trigger the page’s lazy loading, wait for the target images, and check network failures.
Sticky header or controls appear unexpectedly Fixed-position behavior interacts with full-page capture. Test the page pattern directly; hide the element with page CSS or use a targeted capture if appropriate.
Images differ between runs Animations, caret, live data, ads, or asynchronous layout shifts are changing. Stabilize the application state; use Playwright animation controls or masks where appropriate.
One output looks blurrier Different pixel scale, format, or JPEG quality. Compare PNG output and explicitly select Playwright’s scale; account for Puppeteer’s documented options.
Screenshot fails while page is closing Browser or page lifecycle is being changed during capture. Await the screenshot promise before closing the page or browser. Puppeteer documents protections for some BrowserContext page creation and close operations during an in-progress screenshot.
Navigation wait never finishes The page keeps network requests active, such as polling or streaming. Use a less restrictive navigation wait and wait for a page-specific selector or readiness condition.

Puppeteer’s Page.screenshot() returns a Uint8Array by default and a base64 string when encoding: 'base64' is requested. Its API also documents coordination with certain BrowserContext page operations during an in-progress screenshot. See the Page.screenshot reference.

Performance, reliability, and cost

For either library, browser startup, page loading, fonts, images, and page height contribute to the work. Reuse browser processes where your application architecture allows it, close pages and browsers when finished, and avoid full-page captures when a viewport or element image meets the requirement. High device-pixel output increases pixel count and can increase memory and file size; Playwright’s CSS scale provides a way to request one output pixel per CSS pixel.

Make capture reliability explicit: set timeouts, wait for a meaningful page condition, handle navigation and screenshot errors, and save enough metadata to reproduce a mismatch (engine, browser version, library version, viewport, scale, URL, and capture options). For visual regression, use stable test data and decide how dynamic areas are treated. No comparative benchmark or universal cost figure follows from the cited documentation; local compute and browser execution are project-specific.

For hosted captures, ScreenshotNeo’s stated billing rules make only clean shots billable; bot checks, blank pages, timeouts, failed loads, and cache hits are not charged. Plans range from 1,000 free shots monthly to paid tiers starting at $5 for 3,000, with every feature on every plan. Check the product documentation for the currently supported request parameters and usage details.

Which is better for visual regression testing?

Choose based on the test workflow and required controls. Playwright documents screenshot assertions, explicit scale selection, animation behavior, and locator masking, which can be useful for stable comparisons. Puppeteer is a reasonable fit when it already powers the project’s browser automation and its screenshot controls meet the test needs. Neither documentation set proves a blanket image-quality advantage. Run representative pages in the target browser engine and compare repeatability, not just a single image.

FAQ

Are Puppeteer and Playwright screenshots pixel-identical?

That is not guaranteed by the APIs. Browser versions, engine, fonts, scale, page state, and capture settings all matter.

Does full-page capture scroll the page?

The documented intent is to capture the full page or scrollable page. Do not assume this triggers every site’s lazy-loading logic; verify the assets in the result.

Can I compare Chromium, Firefox, and WebKit?

Playwright documents all three engines. Keep the engine fixed when comparing libraries, then run a separate cross-engine comparison if those engines matter to your users.

Which format should I use for visual diffs?

PNG is a practical choice for lossless pixel comparisons. If using JPEG, keep quality settings consistent and expect compression artifacts.