ScreenshotNeo

BlogHow-to

How to Capture WebP Screenshots with Playwright

Save Playwright screenshots as WebP with runnable JavaScript, quality and scale settings, full-page and visual-test examples, troubleshooting, and an API option.

By the ScreenshotNeo team1 October 20269 min read

Set type: 'webp' in Playwright’s screenshot options, or save to a filename ending in .webp. The explicit option is clearest when the output format matters:

await page.screenshot({ path: 'capture.webp', type: 'webp' });

Playwright supports PNG, JPEG, and WebP screenshot output. With a path, the format can also be inferred from the extension. See the Page screenshot API for the complete option list.

Set up a working Playwright WebP capture

1. Create a project and install Playwright

mkdir playwright-webp
cd playwright-webp
npm init -y
npm install playwright
npx playwright install chromium

The browser install command downloads the browser binary used by the script. If your environment already manages Playwright browsers, use that environment’s normal installation process.

2. Save a viewport screenshot as WebP

// screenshot.mjs
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: 'load' });
await page.screenshot({
  path: 'capture.webp',
  type: 'webp',
  quality: 90,
});

await browser.close();
node screenshot.mjs

quality applies to formats other than PNG. For WebP, quality: 100 produces lossless output; lower values are lossy. Playwright does not publish a universal file-size reduction for a particular quality value, so choose quality according to your visual and storage requirements.

WebP screenshot options you will use most

Goal Option or pattern Notes
Choose WebP explicitly type: 'webp' Use this when the format must be obvious in code.
Infer format from a file path: 'image.webp' The screenshot type is inferred from the extension.
Lossless WebP quality: 100 Documented as lossless for WebP.
Lossy WebP quality: 1 through 99 Lower values trade image fidelity for compression; measure your own pages.
Full scrollable page fullPage: true Captures the complete page rather than only the viewport.
Transparent background omitBackground: true Use PNG or WebP; JPEG cannot represent transparency.
Compact CSS-sized pixels scale: 'css' One output pixel per CSS pixel.
Device-pixel detail scale: 'device' The documented default; high-DPI output can be substantially larger.
In-memory bytes Omit path The method returns image bytes instead of writing a file.

Keep scale consistent when comparing visual baselines. Use css for compact, predictable CSS dimensions; use device when device-pixel detail is required.

Capture a full page, an element, or bytes

Full-page WebP

await page.screenshot({
  path: 'full-page.webp',
  type: 'webp',
  fullPage: true,
  quality: 90,
});

Full-page capture scrolls the page to include its complete scrollable content. Pages with lazy-loaded content may need an explicit scroll or a wait strategy before capture so that content has time to render.

A single matching element

const article = page.locator('main');
await article.screenshot({
  path: 'main.webp',
  type: 'webp',
  quality: 100,
});

The locator screenshot captures the element’s bounding box. Make the locator specific enough to avoid matching multiple elements, and wait for the element to be visible before taking the shot.

Return WebP bytes in memory

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

// Example: write the returned Uint8Array to disk
import { writeFile } from 'node:fs/promises';
await writeFile('in-memory.webp', bytes);

In-memory output is useful when uploading directly to object storage, returning an HTTP response, or passing the image to another service without a temporary file.

Control what the page looks like before capture

Screenshot format is only one part of a stable capture. Set the viewport, color scheme, locale, timezone, and other context when those values affect rendering:

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

Wait for the page state your image requires. domcontentloaded is faster but may precede images and web fonts. load waits for the load event. Network idle can be useful for mostly static pages, but long-polling or analytics requests can prevent it from settling. For a specific component, waiting for its selector is usually more deterministic:

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

If animations cause inconsistent frames, disable them with a stylesheet before capture:

await page.addStyleTag({
  content: `*, *::before, *::after {
    animation: none !important;
    transition: none !important;
    caret-color: transparent !important;
  }`,
});

For remote fonts or images, wait for the relevant elements rather than assuming that the first paint is final. Avoid arbitrary delays unless the page has no observable readiness signal; a selector or application state is generally more reliable.

Use WebP with Playwright Test visual snapshots

Playwright Test accepts a WebP snapshot filename:

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

test('homepage visual snapshot', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page).toHaveScreenshot('homepage.webp');
});

You can configure WebP as the default type for unnamed snapshots:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  expect: {
    toHaveScreenshot: {
      type: 'webp',
    },
  },
});

The visual comparisons documentation describes WebP snapshots as lossless and says that toHaveScreenshot() waits for two consecutive captures to match before comparing. Screenshot assertion animation handling is disabled by default, which removes one source of variation. Keep the browser, viewport, scale, fonts, and data stable across baseline and comparison runs.

Quality, scale, and transparency decisions

Choose quality based on the use case

  • Quality 100: use for lossless visual baselines, pixel-sensitive review, or archival output.
  • Middle quality: use when a smaller artifact is more important than exact pixels, then inspect representative pages.
  • Low quality: use only after checking text, thin lines, gradients, and screenshots with detailed imagery.

There is no documented quality value that is optimal for every page, and the supplied documentation does not establish a guaranteed percentage reduction in file size.

Choose a scale deliberately

scale: 'css' creates one image pixel per CSS pixel. scale: 'device' uses device pixels and can produce a much larger image on high-DPI contexts. Select one and use it consistently for visual regression snapshots; otherwise identical CSS layouts can have different pixel dimensions.

Transparent output

await page.screenshot({
  path: 'transparent.webp',
  type: 'webp',
  omitBackground: true,
});

Transparency is relevant to WebP and PNG. JPEG does not support a transparent background, so use a format and downstream image pipeline that preserve alpha when transparency matters.

Version and compatibility checks

The official Playwright API documents WebP output for screenshots. Support details can vary by language binding, browser engine, and installed Playwright version. A release-note result identified Java Page and Locator WebP support in Playwright 1.62, but that does not establish a complete compatibility matrix for every binding or older version. If a project reports an unsupported format, check the release notes and the API page for the exact binding and version, then upgrade Playwright and its browsers together.

Complete reusable capture script

// capture-webp.mjs
import { chromium } from 'playwright';

const target = process.argv[2] ?? 'https://example.com';
const browser = await chromium.launch();
const context = await browser.newContext({
  viewport: { width: 1440, height: 900 },
  deviceScaleFactor: 1,
  colorScheme: 'light',
});
const page = await context.newPage();

try {
  await page.goto(target, {
    waitUntil: 'domcontentloaded',
    timeout: 30_000,
  });
  await page.locator('body').waitFor({ state: 'visible', timeout: 10_000 });
  await page.screenshot({
    path: 'capture.webp',
    type: 'webp',
    quality: 90,
    scale: 'css',
    fullPage: true,
  });
} finally {
  await context.close();
  await browser.close();
}
node capture-webp.mjs https://example.com

Or skip the browser setup

ScreenshotNeo provides a website screenshot API when you want a WebP result without managing Playwright, browser binaries, navigation waits, or capture workers. The API accepts one GET request with a URL and returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo documentation for request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
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 failed: ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', buffer));

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and whether the request was billed. An MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.

Troubleshooting

Symptom Likely cause Fix
Output is PNG or JPEG The path extension or type is different from WebP. Set type: 'webp' and use a .webp path.
“Unknown screenshot type” or unsupported WebP Old Playwright package, browser binary, or binding. Check the API and release notes for your version; upgrade the package and run the browser install command again.
Image is blank Capture happened before the application rendered, or navigation failed. Check the navigation response, wait for a visible readiness selector, and increase the navigation timeout only when the page genuinely needs it.
Lazy images are missing They load only after scrolling or intersection. Use fullPage: true or scroll through the page before capture, then wait for the image elements.
Element screenshot throws a locator error The selector matches zero or multiple elements, or the element is hidden. Use a unique locator and wait for visible; inspect the selector in the same context.
Visual snapshots change between runs Fonts, animations, time, data, viewport, scale, or browser versions differ. Pin the environment, disable animations, wait for stable content, and keep screenshot options identical.
Transparent background is opaque The page has a painted background or the receiving pipeline removes alpha. Use omitBackground: true with WebP or PNG and verify downstream alpha support.
File is larger than expected Device scale, quality 100, full-page dimensions, or detailed content increase bytes. Try scale: 'css', a lower quality after visual review, or an element/viewport capture.
Navigation times out The site is slow, blocked, or keeps network connections open. Use a realistic timeout, wait for a specific selector instead of network idle, and inspect the target URL independently.

Performance, reliability, and cost notes

  • Browser startup: launching Chromium for every image adds overhead. Reuse a browser process and create a fresh context per isolation boundary when capturing many pages.
  • Page size: full-page and device-scale captures require more memory and produce larger files than viewport or CSS-scale captures.
  • Determinism: pin Playwright and browser versions, set a fixed viewport and timezone, use stable test data, and wait for an explicit readiness condition.
  • Network behavior: third-party ads, analytics, and long-running requests can delay or change the final frame. Route or block them only when doing so matches the page you intend to document.
  • Visual tests: use lossless WebP and identical scale for baselines. Compression changes can obscure small rendering differences.
  • Operational cost: self-hosted Playwright costs the compute and storage required by your workers and artifacts. ScreenshotNeo bills only clean shots; bot checks, blank pages, timeouts, failed loads, and cache hits are free, with billing status returned in headers.

FAQ

Can Playwright save screenshots directly as WebP?

Yes. Pass type: 'webp', or use a filename ending in .webp so Playwright infers the type.

Is WebP quality 100 lossless?

Yes, the documented Playwright API describes WebP quality 100 as lossless. Values below 100 are lossy.

Can I use WebP for toHaveScreenshot()?

Yes. Give the snapshot a .webp filename or configure expect.toHaveScreenshot.type as 'webp'.

Should I use CSS or device scale?

Use CSS scale for compact CSS-sized images and device scale for device-pixel detail. Keep the choice consistent for visual comparisons.

Does Playwright guarantee a particular WebP file size?

No. File size depends on page dimensions, content, scale, and quality. Measure representative pages instead of relying on a universal ratio.

What is the simplest hosted alternative?

Use ScreenshotNeo’s one-call API when you do not want to operate browser setup and capture workers. It removes common consent and widget overlays, reports billing and page verdict headers, and includes 1,000 free screenshots per month without a card.