ScreenshotNeo

BlogHow-to

How to Capture a Page Snapshot in Playwright

Capture viewport, full-page, element, buffer, accessibility, and trace snapshots in Playwright with runnable code and troubleshooting.

By the ScreenshotNeo team1 October 20268 min read

Use page.screenshot() for a visual page snapshot. It captures the current viewport by default. Add fullPage: true for the entire scrollable page, provide path to save an image, or omit path to receive an image buffer.

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto('https://example.com', { waitUntil: 'networkidle' });

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

const buffer = await page.screenshot({ type: 'png' });
console.log(`Captured ${buffer.length} bytes`);

await browser.close();

The same API works in Playwright Test, where the test runner can attach screenshots to reports. The official guide documents page screenshots in JavaScript, Python, Java, and .NET: Playwright screenshots.

1. Set up Playwright

npm init -y
npm install -D playwright
npx playwright install

Create snapshot.mjs and run it with node snapshot.mjs. Playwright downloads browser binaries separately from the npm package, so run the install command in each new CI image unless your image already contains the browsers.

2. Capture the viewport

A normal screenshot contains only the visible viewport. Set the viewport explicitly when captures must be repeatable.

import { chromium } from 'playwright';

const browser = await chromium.launch();
const context = await browser.newContext({
  viewport: { width: 1280, height: 800 },
  deviceScaleFactor: 1,
});
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'viewport.png', type: 'png' });
await browser.close();

Supported output formats are PNG, JPEG, and WebP. JPEG and WebP accept a quality value from 0 to 100. PNG is usually the safest format for pixel comparisons because it is lossless.

3. Capture a full-page snapshot

await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({
  path: 'full-page.png',
  fullPage: true,
  animations: 'disabled',
});

fullPage: true captures the full scrollable page rather than only the visible viewport. Very long documents can produce large images and may expose lazy-loading behavior. Scroll or wait for content before capture when a site loads media only after it approaches the viewport.

Full-page checklist

  • Use a fixed viewport and device scale factor.
  • Wait for the page state your capture requires.
  • Disable animations for visual tests.
  • Wait for fonts and critical images before taking the shot.
  • Watch memory use for extremely tall pages.

4. Capture one element

Use a locator when the snapshot should contain a component instead of the whole page.

const header = page.locator('.header');
await header.screenshot({ path: 'header.png' });

locator.screenshot() waits for actionability, scrolls the element into view, and clips the result to the matched element. It can also disable animations, mask regions, inject styles, set a timeout, and choose PNG, JPEG, or WebP output. See the Locator screenshot API.

Element capture with stability controls

await page.locator('[data-testid="price-card"]').screenshot({
  path: 'price-card.png',
  animations: 'disabled',
  mask: [page.locator('.live-counter')],
  style: `
    .ad, .chat-widget { display: none !important; }
  `,
  timeout: 15_000,
});

If the selector matches multiple elements, Playwright reports a strictness error. Make the locator unique with a test id, a role, or a more specific CSS selector.

5. Return a screenshot as a buffer

Omit path when another program should process the image in memory, upload it, or attach it to a test report.

const image = await page.screenshot({ type: 'webp', quality: 85 });

await fetch('https://upload.example.test/images', {
  method: 'POST',
  headers: { 'content-type': 'image/webp' },
  body: image,
});

The returned value is a Node.js Buffer. In Python, Playwright returns bytes; in Java and .NET, use the corresponding byte-array result documented by the language API.

6. Complete Python example

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1280, "height": 800})
    page.goto("https://example.com", wait_until="networkidle")

    page.screenshot(path="viewport.png")
    page.screenshot(path="full-page.png", full_page=True, animations="disabled")

    image_bytes = page.screenshot(type="png")
    with open("memory.png", "wb") as output:
        output.write(image_bytes)

    page.locator(".header").screenshot(path="header.png")
    browser.close()

7. cURL and Node.js alternatives

Playwright itself is a browser automation library, so cURL cannot create a rendered browser screenshot. cURL can request an image from a screenshot service. Node.js can call such an HTTP endpoint directly.

cURL

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

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(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', image));

Python HTTP request

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)

See the ScreenshotNeo documentation for request options and response headers.

8. Make screenshots repeatable

Visual captures become useful for regression testing only when the rendering conditions are controlled.

Source of variation Control
Viewport and device pixels Set viewport and deviceScaleFactor explicitly.
CSS and JavaScript animation Use animations: 'disabled' or inject CSS with style.
Clocks and rotating content Freeze test data or mask the region.
Ads, chat, and personalization Block them in the test context or hide them with injected styles.
Fonts Wait for document.fonts.ready before capture.
Network state Use deterministic fixtures and wait for the required requests.
await page.goto('https://example.com');
await page.evaluate(() => document.fonts.ready);
await page.screenshot({
  path: 'stable.png',
  animations: 'disabled',
  mask: [page.locator('.timestamp'), page.locator('.avatar')],
  style: `
    *, *::before, *::after {
      caret-color: transparent !important;
      transition: none !important;
      animation: none !important;
    }
  `,
});

Masking replaces selected regions with a solid color, which prevents changing data from causing false differences while preserving the rest of the image.

9. Choose the right snapshot type

Need API Output
What a user sees page.screenshot() PNG, JPEG, WebP bytes or file
Entire scrollable document page.screenshot({ fullPage: true }) One tall image
One component locator.screenshot() Clipped image
Accessible structure and names page.ariaSnapshot() or locator.ariaSnapshot() Structured text or JSON
Every action in a test context.tracing Trace archive for Trace Viewer

An ARIA snapshot is not an image. It describes roles, accessible names, and text. Use it when a test or agent needs semantic structure rather than pixels. The accessibility testing guide and Page API describe page and locator variants.

10. Capture snapshots throughout a flow with tracing

await context.tracing.start({
  screenshots: true,
  snapshots: true,
});

await page.goto('https://example.com');
await page.getByRole('button', { name: 'Learn more' }).click();

await context.tracing.stop({ path: 'trace.zip' });

Open trace.zip in Trace Viewer to inspect the action timeline, screenshots, and recorded page snapshots. Tracing is useful when a single final image cannot explain which navigation or action caused a failure. The Trace Viewer guide covers viewing and sharing traces.

11. Troubleshooting

“Timeout exceeded” during screenshot

Cause: the page, locator, or its fonts never reached the expected state. Fix: wait for a specific selector or request, increase the operation timeout for genuinely slow pages, and inspect the trace. Avoid using arbitrary long sleeps as the only synchronization.

The screenshot is blank or incomplete

Cause: navigation failed, content is rendered after capture, or lazy-loaded elements were never brought into view. Fix: check the response status, wait for the required locator, use networkidle only when it reflects the application, and scroll through lazy content before a full-page capture.

Only part of a scrollable element appears

Cause: an element screenshot clips to the element’s current scrollable content. Fix: capture the page, change the element’s scroll position and capture multiple states, or temporarily expand the container with injected CSS.

“Strict mode violation” for an element

Cause: the locator matches more than one node. Fix: narrow it with getByRole, getByTestId, an accessible name, or a more specific selector.

Flaky pixel differences

Cause: animation, timestamps, ads, fonts, viewport differences, or network content. Fix: disable motion, mask dynamic regions, set the viewport and scale, wait for fonts, and use deterministic test data.

Browser executable is missing in CI

Cause: the package is installed but browser binaries were not. Fix: run npx playwright install during image setup, or use an approved Playwright base image and keep its version aligned with your package.

12. Performance, reliability, and cost considerations

  • Reuse a browser process and create contexts per job; launching a new browser for every image adds startup overhead.
  • Capture a locator instead of a full page when only one component is needed. The image is smaller and the work is narrower.
  • Use JPEG or WebP when transfer size matters; use PNG for lossless comparisons and transparent pixels.
  • Full-page captures of very tall pages consume more memory. Split long documents or capture only the required regions.
  • Set explicit navigation and screenshot timeouts, record failures, and retain a trace for intermittent problems.
  • Control concurrency so several large captures do not exhaust CPU or memory in the same worker.
  • Playwright runs locally or in your infrastructure, so your cost is browser runtime, compute, storage, and bandwidth rather than a per-shot API fee.

Or skip the browser setup

ScreenshotNeo provides a single HTTP endpoint for rendered screenshots and PDFs. The call below saves a WebP image; the API documentation lists the available options.

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

Before capture, ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies the result with X-Page-Verdict and X-Billed headers. It also offers an MCP server for AI agents, including Claude and Cursor. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Does a screenshot include the browser chrome?

No. Playwright captures the web page rendered inside the browser, not the browser window, tabs, or address bar.

Can I capture a page before it finishes loading?

Yes, but the result reflects the exact state at capture time. Wait for a selector, a network condition, or a known application-ready signal when complete content matters.

When should I use an ARIA snapshot?

Use it when you need roles, accessible names, and text for assertions, accessibility checks, or agent reasoning. Use a screenshot when visual appearance matters.

Can tracing replace a final screenshot?

Tracing records screenshots and snapshots across actions for debugging. A final screenshot is simpler when you need one artifact for sharing, comparison, or publishing.