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

The core syntax is await page.screenshot({ path: 'screenshot.png' }). It captures the current viewport and returns an image buffer; supplying path writes the file, with the format inferred from the extension. Add fullPage: true for the entire scrollable document, use a locator’s screenshot() method for one element, or provide clip for a rectangular region.

Start with a runnable Playwright screenshot

Install Playwright, install at least one browser, then create a script such as screenshot.js:

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

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({ path: 'screenshot.png' });
  await browser.close();
})();

Run it with node screenshot.js. The relative path is resolved from the process’s current working directory. If you omit path, Playwright still captures the image and returns it as a buffer, which is useful when an application uploads the bytes instead of writing a local file. Chromium is used above; the same Page API works with Firefox and WebKit when you launch those browser types.

Choose the area to capture

Viewport screenshot

Leave out fullPage to capture only what is currently visible in the viewport:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.screenshot({ path: 'viewport.png' });

Full-page screenshot

Set fullPage: true to capture the document’s complete scrollable height rather than just the viewport:

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

Full-page capture is based on the page’s scrollable content. Pages that render content only after scrolling may need an explicit scroll or a wait for the relevant content before the screenshot. Fixed headers, sticky widgets, and animated sections can also appear repeatedly or at different positions unless you stabilize them.

Rectangular clipping

Use clip with x, y, width, and height to capture a region in page coordinates:

await page.screenshot({
  path: 'region.png',
  clip: { x: 120, y: 180, width: 640, height: 360 }
});

The rectangle must be valid for the page. For responsive layouts, a locator is generally more robust than hard-coded coordinates because it follows the element when the layout changes.

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

Element or component screenshot

Locator screenshots wait for the target’s actionability checks and scroll it into view before capturing it. Prefer a locator over the discouraged ElementHandle.screenshot() form:

const button = page.getByRole('button', { name: 'Subscribe' });
await button.screenshot({ path: 'subscribe-button.png' });

const card = page.locator('[data-testid="pricing-card"]');
await card.screenshot({ path: 'pricing-card.webp', type: 'webp' });

A covered element is not magically exposed: if another element occludes it, the captured pixels show the covering content. For a scrollable container, the image contains the portion currently visible inside that container, not all of its off-screen children. Scroll the container first when you need a particular subsection.

Control format, scale, and quality

Option Use Example
type Select PNG, JPEG, or WebP. type: 'jpeg'
path Write the result to a file; the extension also infers the type. path: 'page.webp'
quality Set lossy JPEG/WebP quality. It does not apply to PNG. quality: 80
scale Choose CSS-pixel or device-pixel output where supported. scale: 'css'
omitBackground Use transparency instead of an opaque background where supported. omitBackground: true
fullPage Capture the complete scrollable page. fullPage: true
clip Capture a coordinate rectangle. clip: { x: 0, y: 0, width: 400, height: 300 }

PNG is the documented default. Use JPEG or WebP when smaller files matter and transparency is unnecessary. Set quality only for lossy formats. The exact pixel dimensions also depend on the browser context’s viewport and device scale factor.

Viewport and retina output

const context = await browser.newContext({
  viewport: { width: 1440, height: 900 },
  deviceScaleFactor: 2
});
const page = await context.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'retina.png', scale: 'device' });

A larger device scale factor produces more physical pixels and larger files. Keep viewport, browser engine, fonts, and scale fixed when screenshots are used for visual comparisons.

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.

Make screenshots deterministic

Wait for the page state you actually need

Navigation finishing does not guarantee that a chart, image, or client-rendered component is ready. Wait for a selector, a known state, or a bounded delay:

await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.locator('[data-testid="chart"]').waitFor({ state: 'visible' });
await page.screenshot({ path: 'dashboard.png' });

A selector wait is preferable to an arbitrary sleep. Use a short delay only when an application has a known, unavoidable transition that cannot expose a reliable ready state.

Disable motion and hide unstable content

Animated cursors, carousels, clocks, and rotating advertisements can make two captures differ. Inject a screenshot-only stylesheet and mask dynamic regions:

await page.screenshot({
  path: 'stable.png',
  animations: 'disabled',
  mask: [
    page.locator('[data-testid="live-clock"]'),
    page.locator('.personalized-recommendations')
  ],
  style: `
    *, *::before, *::after {
      animation: none !important;
      transition: none !important;
      caret-color: transparent !important;
    }
  `
});

The injected style affects the screenshot without requiring you to alter the application’s normal stylesheet. Masking replaces the selected regions with a solid mask so volatile values do not create false visual differences. Choose selectors that are present before capture; otherwise wait for them first.

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

Set the environment explicitly

const context = await browser.newContext({
  viewport: { width: 1280, height: 800 },
  colorScheme: 'dark',
  locale: 'en-US',
  timezoneId: 'UTC'
});

Use a fixed viewport, color scheme, locale, timezone, and browser engine for repeatable output. If the page depends on authentication, create the context with the required storage state or log in before capturing. Keep test data stable as well; a screenshot can be perfectly deterministic while still reflecting different backend data on each run.

Use screenshots in Playwright Test

Visual assertions

For visual regression tests, use expect(page).toHaveScreenshot() rather than manually writing files:

import { test, expect } from '@playwright/test';

test('home page matches the baseline', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page).toHaveScreenshot('home.png', {
    fullPage: true,
    animations: 'disabled',
    mask: [page.locator('.live-status')]
  });
});

Playwright Test compares the new image with a stored baseline and reports visual differences. Keep the project’s browser, viewport, and screenshot settings consistent between baseline creation and CI runs. Review intentional UI changes and update baselines deliberately rather than accepting every diff.

Automatic screenshots after tests

Configure the test runner to save screenshots after a test finishes, according to its result:

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.
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    screenshot: 'only-on-failure'
  }
});

This is useful for diagnosing failures without producing an image for every successful test. Other supported policies can capture screenshots for every test or after each test step; choose the least noisy policy that still gives your team the evidence it needs.

Complete capture examples

Save a full-page WebP with a wait and custom viewport

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

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext({
    viewport: { width: 1366, height: 768 },
    deviceScaleFactor: 1
  });
  const page = await context.newPage();

  await page.goto('https://example.com/articles', { waitUntil: 'networkidle' });
  await page.locator('main').waitFor({ state: 'visible' });
  await page.screenshot({
    path: 'articles.webp',
    type: 'webp',
    quality: 82,
    fullPage: true,
    animations: 'disabled'
  });

  await browser.close();
})();

networkidle can be unsuitable for applications with continuous polling or analytics requests. In that case, use a specific readiness locator and avoid waiting forever for an idle network.

Capture the returned buffer

const image = await page.screenshot({ type: 'png' });
// image is a Buffer; upload it, return it from an endpoint, or write it yourself.
require('node:fs').writeFileSync('buffer-capture.png', image);

Common failures and fixes

Symptom Likely cause Fix
Browser executable is missing Playwright package is installed but browser binaries are not. Run the Playwright browser installation command for the engines you use, then rerun the script.
Screenshot is blank or before content appears The script captured before client rendering or a required selector was ready. Wait for a visible, application-specific locator; verify navigation and authentication.
Element screenshot shows another panel The target is covered by an overlay or modal. Dismiss the overlay, wait for it to be hidden, or choose the intended visible locator.
Only part of a long list appears The list is inside a scrollable container. Scroll that container to the required position; a full-page screenshot does not expand an independently scrolling element.
Images or fonts differ in CI Resources, fonts, browser versions, or device scale differ. Pin the environment, wait for critical resources, and use the same browser project for baseline and comparison.
Full-page capture times out The page keeps loading, has very large content, or contains a request that never settles. Use a bounded readiness wait, investigate the hanging resource, reduce capture scope, or increase the operation timeout intentionally.
JPEG quality has no effect Quality is unsupported for PNG. Set type: 'jpeg' or type: 'webp' before setting quality.
Visual test fails with tiny differences Animation, time, random data, or responsive dimensions changed. Disable motion, mask dynamic areas, freeze test data, and standardize viewport, locale, timezone, and scale.

Performance, reliability, and file strategy

  • Capture only what you need. Viewport and element images are normally cheaper in time and memory than very tall full-page images.
  • Reuse a browser process. Launching a browser for every URL adds startup overhead; create contexts and pages within one controlled process when capturing batches.
  • Bound waits. A selector wait with a sensible timeout fails clearly; an unrestricted network-idle wait can hang on apps that poll continuously.
  • Control concurrency. Running too many pages at once increases CPU, memory, and resource contention. Start with a small worker count and measure in your own environment.
  • Keep artifacts identifiable. Include the route, viewport, browser project, and commit or build identifier in filenames so a failed comparison can be reproduced.
  • Protect secrets. Do not put access tokens, passwords, or private customer data into screenshot paths, test titles, or uploaded artifacts.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If you need a screenshot from a URL rather than browser automation in your own process, ScreenshotNeo provides a website screenshot API and MCP server. A single GET request returns PNG, JPEG, WebP, or a PDF:

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)
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}`);

See the ScreenshotNeo documentation for request options. Before capture it can accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and whether the request was billed. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

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

ScreenshotNeo includes full-page capture with lazy-image loading, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF paper and page controls, custom CSS and JavaScript, pre-capture clicks, selector or network-idle waits, ad/tracker/request blocking, custom headers and cookies, user-agent and Authorization settings, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, an OpenAPI specification, and compatibility with parameter names used by other screenshot APIs.

Plan Monthly screenshots Price
Free 1,000 $0, no card
Starter 3,000 $5
Growth 15,000 $15
Pro 60,000 $39
Scale 250,000 $99
Business 1,000,000 $249

Every feature is available on every plan, and yearly billing provides two months free. Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

Playwright screenshot syntax FAQ

What does page.screenshot() return?

It returns a buffer containing the encoded image. When you pass path, Playwright also writes that image to the specified file.

Can I screenshot a PDF with Playwright’s screenshot method?

No. page.screenshot() creates an image. Use a PDF-capable workflow when the required output is a PDF, or use ScreenshotNeo’s capture_pdf MCP tool or PDF options for URL-based capture.

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

Why is a locator screenshot preferable to an element handle?

Locators include Playwright’s actionability and auto-wait behavior and are the recommended element-capture interface; the ElementHandle screenshot API is discouraged.

How do I make a screenshot test useful in CI?

Keep browser, viewport, scale, fonts, locale, timezone, and test data consistent; disable animation and mask volatile regions; then review baseline changes as code changes rather than accepting every diff automatically.

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.