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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use Playwright’s page.screenshot() with a clip rectangle when you need only part of a page:

await page.screenshot({
  path: 'clipped.png',
  clip: { x: 100, y: 200, width: 600, height: 400 }
});

x and y are the rectangle’s top-left coordinates in CSS pixels; width and height define its size. For a single DOM node, locator.screenshot() is usually safer because Playwright calculates the element’s bounds for you.

Choose the right screenshot scope

Playwright offers four useful scopes. Pick the narrowest one that matches the artifact you need.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Need API Coordinate stability Typical use
Current viewport page.screenshot() with neither clip nor fullPage Depends on viewport and scroll position What a user currently sees
Fixed rectangle page.screenshot({ clip: { x, y, width, height } }) Explicit geometry; sensitive to layout changes A region spanning several elements
One element locator.screenshot() Tracks the element’s rendered bounds Cards, headers, charts, buttons or components
Entire scrollable page page.screenshot({ fullPage: true }) Page height and lazy content can change Long-page documentation or audit artifacts

fullPage defaults to false. Do not combine it with a clip when your intention is a simple, stable region: use one scope deliberately and verify the output.

Clip a rectangle in JavaScript

This complete example launches Chromium, navigates to a page, waits for a visible target, and writes a PNG containing a 600-by-400 CSS-pixel rectangle.

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 900 } });

await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({
  path: 'clipped.png',
  clip: { x: 100, y: 200, width: 600, height: 400 },
  scale: 'css'
});

await browser.close();

The rectangle is measured against the page’s rendered coordinate system. A fixed clip therefore includes whatever happens to occupy those coordinates at capture time. If a cookie banner, responsive breakpoint, font swap or late-loading image changes the layout, the same numbers may capture a different area.

Validate geometry before capturing

Make the rectangle explicit and reject invalid dimensions before calling the API. This avoids confusing protocol errors and makes configuration mistakes obvious.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const clip = { x: 100, y: 200, width: 600, height: 400 };

for (const [name, value] of Object.entries(clip)) {
  if (!Number.isFinite(value) || value < 0) {
    throw new Error(`Invalid clip.${name}: ${value}`);
  }
}

await page.screenshot({ path: 'clipped.png', clip });

Use non-negative values for all four fields. Keep the rectangle inside the intended viewport when you want predictable output; if it extends beyond the visible area, test the result on the browser version and page layout used in CI.

Screenshot one element without manual coordinates

For a single component, prefer a locator. Playwright scrolls the element into view when necessary and captures its rendered box, so a responsive layout can move the component without invalidating hard-coded coordinates.

const card = page.locator('.pricing-card').first();
await card.waitFor({ state: 'visible' });
await card.screenshot({ path: 'pricing-card.png', scale: 'css' });

You can target by role, label or test ID instead of a fragile class:

await page.getByRole('article', { name: 'Pro plan' })
  .screenshot({ path: 'pro-plan.png' });

Element screenshots are still affected by animations, changing text, web fonts and content that has not finished loading. Wait for the state your test actually requires rather than relying only on a fixed sleep.

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

Capture a region that is below the fold

A clip uses page coordinates, while a viewport screenshot shows the current scroll position. If the rectangle is below the fold, scroll the relevant element into view first, or use an element screenshot.

const chart = page.locator('#revenue-chart');
await chart.scrollIntoViewIfNeeded();
await chart.screenshot({ path: 'chart.png' });

If you need the whole document rather than one region, use:

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

Full-page capture can trigger additional layout and lazy-loading behavior. It is not a crop operation: it produces the full scrollable page.

Control pixel density and transparency

scale: 'css' versus scale: 'device'

scale: 'css' produces one output pixel per CSS pixel, making files smaller and dimensions easier to reason about. scale: 'device' uses device pixels, which can produce sharper but larger images on high-DPI contexts. Choose one consistently for visual regression baselines.

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.
await page.screenshot({
  path: 'css-scale.png',
  clip: { x: 0, y: 0, width: 800, height: 500 },
  scale: 'css'
});

await page.screenshot({
  path: 'device-scale.png',
  clip: { x: 0, y: 0, width: 800, height: 500 },
  scale: 'device'
});

Transparent PNG output

omitBackground: true hides the default white background and allows transparency. Use PNG for this; JPEG cannot represent transparency.

await page.screenshot({
  path: 'transparent.png',
  clip: { x: 100, y: 100, width: 500, height: 300 },
  omitBackground: true,
  type: 'png'
});

Make clipping deterministic

Most incorrect crops are timing or layout problems, not screenshot API problems. Establish the same browser state before every capture.

  1. Set a known viewport. A responsive breakpoint can move the target or change its size.
  2. Navigate and wait for the required state. Use a meaningful locator, a navigation condition, or a controlled network-idle wait.
  3. Dismiss overlays when appropriate. Consent banners, chat launchers and newsletter dialogs can cover the rectangle.
  4. Wait for fonts and images. For critical visual work, wait for document.fonts.ready and for specific images to complete.
  5. Disable motion. Inject a test stylesheet that sets transitions and animations to zero when movement would change pixels.
  6. Use stable selectors for elements. Prefer roles or test IDs over generated class names.
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.locator('#dashboard').waitFor({ state: 'visible' });
await page.evaluate(async () => {
  await document.fonts.ready;
  for (const image of document.images) {
    if (!image.complete) await new Promise(resolve => {
      image.addEventListener('load', resolve, { once: true });
      image.addEventListener('error', resolve, { once: true });
    });
  }
});
await page.addStyleTag({ content: `
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    caret-color: transparent !important;
  }
` });

Use a fixed timezone, locale, color scheme and authentication state when those values affect the rendered page. Keep test data stable so text wrapping does not alter the clip.

Use clipping in visual regression tests

Playwright Test’s expect(page).toHaveScreenshot() accepts the same clipping and full-page concepts. It waits until two consecutive screenshots are identical before comparing the result, which helps settle minor rendering changes. This assertion is available in the Playwright test runner, not in arbitrary scripts.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { test, expect } from '@playwright/test';

test('dashboard summary is stable', async ({ page }) => {
  await page.goto('/dashboard');
  await page.getByRole('heading', { name: 'Summary' }).waitFor();

  await expect(page).toHaveScreenshot('summary.png', {
    clip: { x: 80, y: 120, width: 900, height: 500 },
    animations: 'disabled',
    scale: 'css'
  });
});

Keep clip geometry, browser version, viewport and test data stable. A rectangle is useful when you want to exclude timestamps, rotating banners or unrelated navigation, but it can hide a regression outside the selected area.

Python equivalent

The Python API uses the same fields. This synchronous example saves a clipped PNG.

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1280, "height": 900})
    page.goto("https://example.com", wait_until="networkidle")
    page.screenshot(
        path="clipped.png",
        clip={"x": 100, "y": 200, "width": 600, "height": 400},
        scale="css",
    )
    browser.close()

For an element:

card = page.locator(".pricing-card").first
card.wait_for(state="visible")
card.screenshot(path="card.png")

Common failures and fixes

“Element is not visible” or a zero-size screenshot

The locator may match a hidden copy, the page may not have finished rendering, or a parent may have zero dimensions. Inspect the matched count, wait for visibility, and choose a visible locator. Scroll it into view before capture.

The crop contains the wrong content

Hard-coded coordinates describe a location, not a semantic element. Set the viewport, wait for fonts and data, and replace the rectangle with locator.screenshot() when the subject is one node.

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

The screenshot is clipped at the viewport edge

A viewport capture only contains what the browser can render in that view. Scroll the target into view, capture the element, or use fullPage: true when the entire document is required.

Images or fonts are missing

Wait for the specific resources, verify that the test environment can reach them, and avoid taking the screenshot immediately after navigation. A network-idle condition alone may not cover a font served from a separate connection or an image loaded by script.

Visual tests fail intermittently

Disable animations, freeze clocks and random data where possible, use a fixed viewport and device scale, and keep browser and operating-system dependencies consistent. Do not enlarge a diff threshold until you know which pixels are changing.

Transparent output appears white

Use PNG, set omitBackground: true, and ensure the page or target actually has transparent pixels. JPEG output cannot preserve an alpha channel.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, files and operational trade-offs

  • Element capture is usually smallest. It avoids storing unrelated page pixels and reduces visual-diff noise.
  • Rectangles are efficient but maintenance-heavy. They are excellent for fixed dashboards or multi-element regions, but responsive redesigns require recalculating coordinates.
  • Full-page capture costs more memory and time. Long pages, large images and high device scale increase output size.
  • CSS scale is a practical default. Use device scale when consumers require physical high-density pixels.
  • Choose output type deliberately. PNG supports lossless pixels and transparency; JPEG is smaller for photographic content but has no transparency.

For repeatable pipelines, name files with the test or URL, retain the browser version used to create baselines, and clean temporary screenshots after upload or comparison.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP or PDF, while its cleaning steps accept consent banners and remove more than 60 known consent platforms, newsletter popups and chat widgets before capture. Each step can be turned off.

Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers.

cURL

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)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
const buffer = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', buffer));

See the ScreenshotNeo documentation for request options. Its 63 options include full-page and CSS-selector element capture, dark mode, device presets or custom viewports, retina scale, PDF paper settings, custom CSS and JavaScript, click and wait actions, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration. AI agents can use the MCP tools take_screenshot, get_page_info and capture_pdf from Claude, Cursor or another MCP client.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Plan Included shots Price
Free 1,000 per month $0, no card
Starter 3,000 $5
Growth 15,000 $15
Pro 60,000 $39
Scale 250,000 $99
Business 1,000,000 $249

Yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to start with 1,000 screenshots a month and no card.

Frequently Asked Questions

Can a clip include several separate elements?

Yes. Define a rectangle that covers them all, or wrap the elements in a temporary container and capture that container when the layout permits.

Should I use a clip or locator screenshot for a component library?

Use a locator screenshot for one component; use a clip when the visual contract intentionally covers a fixed region containing multiple components.

Can Playwright return screenshot bytes instead of writing a file?

Yes. Omit the path and keep the returned buffer in memory for an upload, pixel comparison or other post-processing step.

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

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.