ScreenshotNeo

BlogHow-to

How to Take a Playwright Screenshot in Chromium Headless Mode

Capture a page with Playwright in headless Chromium, configure full-page and image options, and troubleshoot common screenshot issues.

By the ScreenshotNeo team4 October 20267 min read

To take a screenshot with Playwright in headless Chromium, launch Chromium, navigate a page, and call page.screenshot(). Chromium is headless by default in Playwright. Install the package and its browser, then run the example below.

npm install playwright
npx playwright install chromium
const { chromium } = require('playwright');

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

This saves a viewport-sized PNG to screenshot.png. The Playwright Page API documents the screenshot options. For current Chromium installation and headless-mode details, see Playwright browsers and BrowserType.

1. Choose what the screenshot contains

Viewport or full page

By default, Playwright captures the current viewport. Use fullPage: true to capture the full scrollable page:

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

Full-page capture can produce a very tall image on long pages. If you need a specific area instead, bring it into view and capture that element with its screenshot method:

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

Use a selector that identifies one element reliably. If the selector matches nothing or matches multiple elements where one is expected, inspect the page structure and narrow the locator.

Save to disk or use the returned bytes

Passing path writes the image to a file. Without a path, page.screenshot() returns an image buffer, which you can send to another function, store, or inspect without writing a file:

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

2. Set image format, quality, and scale

Playwright supports PNG, JPEG, and WebP. PNG is the default. When saving to a path, the file extension can determine the format. You can set type explicitly when you want the format to be clear in code.

await page.screenshot({ path: 'page.webp', type: 'webp', quality: 85 });
await page.screenshot({ path: 'page.jpg', type: 'jpeg', quality: 80 });

Quality applies to JPEG and WebP, not PNG. The documented default quality is 80 for JPEG and 100 for WebP; WebP at that default is lossless. Lower lossy-image quality can reduce file size, but may introduce visible artifacts. Keep PNG for sharp text or when lossless output matters.

The documented default scale is 'device'. Device scale uses device pixels and can create a larger image on high-DPI settings. Choose 'css' for one output pixel per CSS pixel:

await page.screenshot({ path: 'page-css-scale.png', scale: 'css' });

Pick the scale and format for the image’s use: visual inspection, a test artifact, or delivery to an application can have different size and fidelity needs.

3. Make captures more repeatable

Pages can change while the screenshot is being captured. Disable animations for a steadier image, and wait for the page state your use case needs before taking the shot.

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

With animations: 'disabled', Playwright fast-forwards finite animations and cancels infinite animations for the capture, then resumes them afterward. A screenshot style option can inject CSS to hide or adjust dynamic content such as a blinking cursor or timestamp. Keep any injected style limited to capture presentation so the image still represents the intended page.

For pages that fetch content after initial load, wait for a meaningful locator rather than assuming navigation completion means the content is ready:

await page.goto('https://example.com');
await page.locator('main h1').waitFor({ state: 'visible' });
await page.screenshot({ path: 'ready.png' });

4. Headless Chromium setup details

Playwright launches Chromium headlessly by default; you do not need to pass a headless flag for the common case. Playwright documents a regular Chromium build for headed operation and a separate Chromium headless shell. For the newer headless mode, its browser documentation describes opting into the chromium channel. These details can vary by Playwright release, so check the browser docs for the version installed in your project.

If you only need the headless shell, the documented installation command is:

npx playwright install --with-deps --only-shell

Use this when that browser build matches your execution environment and requirements. On CI systems, install the browser dependencies appropriate to the OS and keep the Playwright package and browser installation aligned.

5. Complete runnable variants

Node.js with cleanup on errors

The earlier example uses try/finally so Chromium closes if navigation or capture fails. This pattern is useful in scripts and workers because a failed page does not leave the browser process running.

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

async function capture(url, outputPath) {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
    await page.goto(url, { waitUntil: 'load', timeout: 30000 });
    await page.screenshot({ path: outputPath, fullPage: true, animations: 'disabled' });
  } finally {
    await browser.close();
  }
}

capture('https://example.com', 'example.png').catch((error) => {
  console.error('Screenshot failed:', error);
  process.exitCode = 1;
});

Python

Install the Python package and its Chromium browser, then run this script:

pip install playwright
playwright install chromium
import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        try:
            page = await browser.new_page(viewport={"width": 1440, "height": 900})
            await page.goto("https://example.com", wait_until="load", timeout=30000)
            await page.screenshot(path="example.png", full_page=True, animations="disabled")
        finally:
            await browser.close()

asyncio.run(main())

cURL

cURL does not launch Playwright or Chromium, so it cannot perform this local browser workflow by itself. To use cURL for a screenshot, call a screenshot service API instead. ScreenshotNeo’s endpoint accepts a URL and returns an image; its API documentation lists supported parameters.

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

6. Direct screenshots versus visual assertions

page.screenshot() creates an image for your script. A Playwright Test screenshot assertion checks a page against an expected visual snapshot. expect(page).toHaveScreenshot() waits until two consecutive screenshots match before comparing with the expected snapshot, and it is intended for the Playwright Test runner.

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

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

See the visual comparisons guide and PageAssertions API. Use direct capture when you need an image file or buffer; use the assertion when a test should detect visual changes.

7. Troubleshooting

Symptom Likely cause Fix
Chromium executable is missing The Playwright package is installed but its browser build is not. Run npx playwright install chromium (or the matching Python install command) in the environment that runs the script.
Browser fails to start in CI or a container Required system dependencies may be absent, or the installed browser build may not match the runtime setup. Install the documented browser dependencies for that environment and use the Playwright browser installation instructions for the installed release.
Screenshot is blank or missing page content The page may not have finished loading its meaningful content when capture begins. Wait for a visible locator or other page-specific readiness condition before calling screenshot().
Capture hangs or navigation times out The site may keep connections open, respond slowly, or fail to load. Set an explicit navigation timeout, choose a suitable waitUntil condition, and wait for the specific content needed instead of waiting for every background request.
Full-page image is unexpectedly tall fullPage: true captures the full scrollable page. Remove fullPage for viewport output or capture a specific element.
Screenshot differs across machines Rendering can vary by operating system, browser version, settings, hardware, power source, and headless mode. Generate and compare visual baselines in the same environment, as recommended by Playwright’s visual comparison guide.
Output file format is unexpected The path extension or explicit type may select a different format than intended. Use a matching extension and set type explicitly; set quality only for JPEG or WebP.

8. Performance, reliability, and cost

For a single capture, the main work is starting or reusing a browser, loading the target page, and encoding the image. Reuse a browser process for multiple pages in a controlled worker when appropriate, and close pages and browsers when finished. Full-page captures and device-pixel scale can produce larger images and require more memory than viewport captures at CSS scale.

Reliability depends on the page and the runtime as well as the screenshot call. Use explicit timeouts, wait for the content you need, close browser resources in cleanup code, and keep browser versions and visual-test environments consistent. A screenshot script has no per-image service fee, but you operate the browser and its compute environment.

Or skip the browser setup

With ScreenshotNeo, one GET request returns a screenshot or PDF. Its API accepts familiar screenshot parameter names, and the ScreenshotNeo docs describe the endpoint and options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

Cookie banners are accepted and removed before capture, along with known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed, and response headers report the page verdict and billing status. An MCP server gives AI agents tools to take screenshots, inspect page information, and capture PDFs. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

FAQ

Does Playwright use headless mode automatically?

Yes. Chromium launches headlessly by default in Playwright unless you configure a headed launch.

Can I take a screenshot without writing a file?

Yes. Omit path; the screenshot call returns image bytes in a buffer.

Why do visual test snapshots change on another machine?

Rendering can differ across operating systems, browser versions, hardware, settings, and headless modes. Keep baseline generation and comparison in the same environment.

Does cURL run Playwright?

No. cURL makes HTTP requests; use Playwright code to run Chromium locally, or call a screenshot API with cURL.