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


