How to Screenshot a Page in Chromium Using Playwright
Capture a Chromium page with Playwright, save viewport or full-page screenshots, target elements, and handle common screenshot options and failures.
Use Playwright’s Chromium browser, navigate a Page to the URL, and call page.screenshot(). Set path to save the image. By default, Playwright captures the visible viewport; set fullPage: true to capture the whole scrollable page.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
} finally {
await browser.close();
}
})();
This is a runnable Node.js example. Install Playwright in a project with npm install playwright. The package downloads browser binaries during installation; if Chromium is missing in an environment, run npx playwright install chromium. See the Page.screenshot() API and Playwright screenshot guide for the current API details.
1. Set up and take the first screenshot
- Create a project directory and initialize Node.js if needed:
npm init -y. - Install Playwright:
npm install playwright. - Save the example in
screenshot.js. - Run it with
node screenshot.js. The image is written relative to the process’s current working directory.
chromium.launch() starts Chromium. browser.newPage() creates a page in a new browser context. page.goto() navigates to the target, and page.screenshot() captures the rendered page. The finally block closes Chromium even if navigation or capture throws an error.
2. Choose what to capture
Visible viewport
await page.screenshot({ path: 'viewport.png' });
This captures the currently visible viewport. The default is fullPage: false.
Full scrollable page
await page.screenshot({ path: 'full-page.png', fullPage: true });
Playwright captures the full scrollable page as a tall image. Pages with very large or effectively infinite scrolling areas can produce huge images and take longer to process. For pages that load content only as you scroll, scroll through the page first and wait for the content you need before capturing.
One element
await page.locator('header').screenshot({ path: 'header.png' });
A locator screenshot crops to the element’s bounding box. Prefer a stable selector such as a test ID or semantic locator when the site provides one. If the locator matches no element, the operation times out waiting for it; wait for or correct the selector before capturing.
Rectangular clipped region
await page.screenshot({
path: 'region.png',
clip: { x: 0, y: 0, width: 800, height: 500 }
});
clip specifies a rectangle in page coordinates. The requested area must be valid for the page; choose coordinates and dimensions that cover the content you intend to capture.
3. Control navigation and page readiness
A screenshot captures what has rendered when the screenshot call runs. Navigation completing does not guarantee every image, animation, asynchronous widget, or application data request has finished. Choose a readiness condition that matches the page rather than adding an arbitrary long delay to every capture.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto('https://example.com', { waitUntil: 'load', timeout: 30000 });
await page.locator('main').waitFor({ state: 'visible', timeout: 10000 });
await page.screenshot({ path: 'ready.png', fullPage: true });
} finally {
await browser.close();
}
})();
waitUntil controls the navigation lifecycle condition. The example uses load, then waits for a page-specific element. For a client-rendered page, that element is often a better signal that the content you need is present. Use a finite timeout so an unavailable page does not hang the job indefinitely. A fixed delay can be useful for a known delayed visual effect, but it is less reliable than waiting for a meaningful condition.
4. Screenshot options you are likely to need
| Option | What it does | When to use it |
|---|---|---|
path |
Writes the screenshot to a file. The extension can determine the image format. | Saving an artifact such as page.png. |
fullPage |
Captures the full scrollable page instead of only the viewport. | Page archives, reports, or long-page review. |
type |
Chooses png, jpeg, or webp. |
Choose a supported output format explicitly when needed. |
quality |
Sets lossy image quality for JPEG or WebP; it does not apply to PNG. | Reducing lossy-image file size. Check the API’s supported range for your installed version. |
clip |
Captures a specified rectangle. | A region of the page rather than the viewport or full document. |
scale |
Uses CSS pixels (css) or device pixels (device) for output scaling. |
Control image dimensions and pixel density. |
animations |
disabled fast-forwards finite animations and cancels infinite ones for the capture; allow leaves them running. |
Reduce animation-related differences in snapshots, or preserve the current animation state. |
caret |
Controls whether a text caret is visible in the screenshot. | Keep focused inputs from showing a blinking caret in a capture. |
mask, maskColor |
Overlays a color on matching locator regions; the default mask color is pink. | Cover dynamic or sensitive visual regions in screenshot output. |
style |
Applies a stylesheet while taking the screenshot. | Hide transient elements or normalize a page for capture without changing application code. |
omitBackground |
Omits the default background to allow transparency; it is not applicable to JPEG. | Transparent PNG or WebP output where supported by the selected format. |
Example combining common options:
await page.screenshot({
path: 'stable.webp',
type: 'webp',
fullPage: true,
animations: 'disabled',
caret: 'hide',
mask: [page.locator('[data-dynamic]')],
maskColor: '#222222'
});
Check the API reference for exact option types and version support. When using omitBackground, choose a format that supports transparency; JPEG does not.
5. Save to a buffer instead of a file
Omit path to receive screenshot bytes as a buffer. This is useful when uploading the image, returning it from a service, or processing it without first writing a file.
const imageBuffer = await page.screenshot({ type: 'png' });
console.log(`Captured ${imageBuffer.length} bytes`);
For an HTTP response in a Node.js server, send the buffer with the appropriate content type (for PNG, image/png). Manage memory if you capture many pages concurrently: buffers remain in memory until references are released.
6. Set viewport, device scale, and browser context
Screenshot dimensions depend on the page viewport and screenshot scale. Define the viewport when you create the page or context to make runs predictable:
const context = await browser.newContext({
viewport: { width: 1365, height: 768 },
deviceScaleFactor: 1
});
const page = await context.newPage();
To emulate a device profile, use a device descriptor supported by the installed Playwright version, or set viewport and device scale explicitly. A viewport controls the CSS layout width and height; the device scale factor affects device-pixel rendering. Keep both consistent when generating visual baselines.
Use separate browser contexts for independent sessions, cookies, or storage. If a page requires authentication, establish the relevant state in the context before capture. Treat screenshots as potentially sensitive artifacts: pages may display account information, personal data, or tokens.
7. cURL, Python, and Node.js with ScreenshotNeo
Playwright runs a local Chromium browser. If your task is simply to request a website screenshot without managing browser installation, a screenshot API can handle the capture remotely. ScreenshotNeo is a website screenshot API and MCP server for developers. Its API accepts one GET request with a URL and can return PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for request options.
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(`Screenshot request failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
Keep the API key on a server or in a protected environment variable; do not expose it in browser-side code or a public repository. ScreenshotNeo also accepts many parameter names used by other screenshot APIs, which can make switching integrations easier.
8. Or skip the browser setup
ScreenshotNeo handles the remote capture in one API call. Cookie banners are accepted like a visitor and more than 60 known consent platforms, newsletter popups, and chat widgets are removed before the shot; each of those steps can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are never billed, and responses identify the page verdict and billing status in headers. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Read the API docs and sign up for 1,000 free screenshots a month, with no card.
9. Visual regression and reproducibility
For automated visual checks, Playwright Test provides expect(page).toHaveScreenshot(). It waits for two consecutive screenshots to match and compares the resulting capture with an expected baseline. Screenshot assertions are a Playwright Test runner feature; they are not a replacement for the lower-level page.screenshot() call.
const { test, expect } = require('@playwright/test');
test('homepage visual baseline', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('homepage.png');
});
Install the test runner with npm install --save-dev @playwright/test and run tests with npx playwright test. For stable comparisons, use the same operating system, browser version, viewport, device scale, fonts, and relevant browser settings for both baseline generation and comparison. Rendering can vary with the host OS, browser version, settings, hardware, power source, and headless mode; environment differences can create pixel changes unrelated to an application change. See the Playwright visual comparisons guide.
10. Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
Cannot find module 'playwright' |
Playwright is not installed in this project, or the command runs from a different project directory. | Run npm install playwright in the project and execute the script from that project. |
| Browser executable is missing | The Chromium binary was not installed or is unavailable in the environment. | Run npx playwright install chromium. In CI or a container, follow the Playwright installation guidance for that environment. |
| Navigation times out | The server is slow, unreachable, or the chosen lifecycle event does not occur promptly. | Check the URL and network access, set an appropriate finite timeout, and wait for a page-specific condition when that is the real readiness requirement. |
| Screenshot is blank or incomplete | The capture occurred before client-side content appeared, or the page showed an error, consent layer, or blocked content. | Wait for a visible content locator, inspect the page state, and account for authentication, consent, or network dependencies. |
| Element screenshot times out | The locator does not match, is hidden, or never becomes available. | Confirm the selector, wait for the expected state, and ensure the element is visible before taking its screenshot. |
| Full-page image is unexpectedly large | The document is long, has an expanding layout, or includes large images. | Capture a viewport or target element, or reduce output dimensions and choose an appropriate format and scale. |
| Screenshot differs between runs | Animations, timestamps, dynamic data, fonts, environment, or browser versions changed. | Disable animations, mask or normalize dynamic regions, and keep the baseline and comparison environment consistent. |
| Transparent output has a solid background | omitBackground was not set, or the selected format does not support transparent output. |
Use omitBackground: true with PNG or a supported transparency-capable format; JPEG cannot omit the background. |
| Output file is missing | The relative path is resolved from a different working directory, or the parent directory does not exist. | Log or resolve the process working directory, use an absolute path if appropriate, and create the destination directory before capture. |
11. Performance, reliability, and cost
- Reuse browser processes for batches. Launching Chromium is comparatively expensive. For multiple captures in one job, reuse a browser and create isolated pages or contexts as needed; close them when finished.
- Bound concurrency. Each page consumes memory and browser resources. Capture a controlled number of pages in parallel, especially for full-page images.
- Use the smallest capture that meets the need. Viewport or element screenshots are generally less work than capturing a very tall page. Avoid unnecessarily high device scale when the output does not need it.
- Wait deliberately. A specific selector is usually more reliable than sleeping for a guessed duration. Set navigation and locator timeouts so slow or broken pages have a defined failure path.
- Make artifacts traceable. Use deterministic filenames or attach URL and capture metadata in your own workflow. Avoid overwriting useful evidence unintentionally.
- Budget storage and transfer. Full-page and high-resolution images can be large. Select PNG for lossless output, or JPEG/WebP when lossy compression is acceptable, and consider whether a buffer or file is the better destination.
- Account for local operating cost. Self-hosted Playwright requires browser installation and machine resources. ScreenshotNeo pricing is Free for 1,000 shots per month with no card; Starter is $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan.
12. Frequently asked questions
Does Playwright take a screenshot of the browser window or only the page?
page.screenshot() captures page content. It does not capture browser chrome such as the address bar and tabs.
Can I capture a screenshot without saving it to disk?
Yes. Omit path; the call returns image bytes as a buffer.
Can I use Playwright screenshots in a test?
Yes. Use page.screenshot() for an image artifact or Playwright Test’s toHaveScreenshot() assertion for visual comparisons.
Can Playwright capture PDFs with this method?
No. page.screenshot() produces an image. For PDF output, use Chromium’s page PDF functionality or a screenshot service that supports PDF capture.
Why does a screenshot look different on another machine?
Browser version, operating system, fonts, graphics environment, and page state can change rendering. Keep the capture environment and settings consistent for comparisons.


