How to Capture a Screenshot of a Page at a Specific URL with Playwright
Use Playwright to open a URL and capture its viewport, full page, a selected region, or an element. Learn the options, fixes, and when to use a screenshot API.
To capture a screenshot of a page at a specific URL with Playwright, navigate to it with page.goto(), then call page.screenshot(). Set path to save an image file, or omit it to receive image bytes. By default, Playwright captures the visible viewport; use fullPage: true for the full 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 follows the sequence documented in the Playwright Page API. Install Playwright and its browser before running the script; setup commands and browser installation details are in the official installation guide.
1. Set up and run the capture
For a small Node.js script, install the Playwright package, install the browser you intend to use, and save the example as screenshot.js. The Chromium example below is easy to adapt to another supported browser.
npm install playwright
npx playwright install chromium
node screenshot.js
Use an absolute URL including the scheme, such as https://example.com. The navigation call resolves when the requested load condition is reached; choose a more appropriate condition when the page’s useful content appears later.
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 });
} finally {
await browser.close();
}
}
capture('https://example.com', 'page.png').catch(error => {
console.error(error);
process.exitCode = 1;
});
For a one-off script, closing the browser in finally releases its process even if navigation or capture fails. In a long-running worker, reuse a browser and create a fresh context or page per job rather than launching a browser for every URL.
2. Choose the capture scope and output
Select the smallest capture that answers your need. A viewport is usually right for a preview; full-page capture is useful for a document snapshot; a clip or locator screenshot avoids capturing irrelevant content.
| Need | Option or method | Notes |
|---|---|---|
| Visible viewport | page.screenshot() |
Default capture scope. |
| Full scrollable document | fullPage: true |
Captures beyond the current viewport. |
| Coordinates within the page | clip: { x, y, width, height } |
Use a positive rectangle in page CSS pixels. |
| One element | page.locator(selector).screenshot() |
Wait for and capture the matched element. |
| File output | path: 'page.png' |
Image type is inferred from the extension. |
| In-memory output | Omit path |
Returns a buffer in Node.js. |
Full page
await page.screenshot({ path: 'full-page.png', fullPage: true });
The Page API describes fullPage as capturing the full scrollable page instead of only the current viewport. Very long pages can produce large images and take longer to encode. Lazy-loaded sections may not appear unless they have been loaded by scrolling or by page-specific behavior; do not assume a full-page capture triggers every site’s lazy loading.
Clip a region
await page.screenshot({
path: 'region.png',
clip: { x: 0, y: 0, width: 800, height: 500 }
});
A clip is a rectangular region measured in CSS pixels. Ensure its dimensions are positive and its position corresponds to the content you want. For a region tied to an element’s current location, a locator screenshot is often more robust than hard-coded coordinates.
Capture a locator
await page.locator('.header').screenshot({ path: 'header.png' });
Replace .header with a selector that uniquely identifies the intended element. Locator screenshots are useful when page layout shifts, because the element is targeted by its selector rather than fixed coordinates. If the selector matches no element, is ambiguous in your intended use, or the element never becomes visible, inspect the page and choose a stable selector or wait for the relevant state.
Save a file or process bytes
// Save directly to disk
await page.screenshot({ path: 'page.webp' });
// Or keep the image in memory
const imageBuffer = await page.screenshot();
// Pass imageBuffer to a storage client or image-processing library.
Playwright infers PNG, JPEG, or WebP from the path extension. JPEG quality can be configured; PNG does not use the quality option. With no path, the result is a buffer that your application can upload or process without first writing a file.
Pixel scale and quality
await page.screenshot({
path: 'page.jpg',
type: 'jpeg',
quality: 80,
scale: 'css'
});
scale: 'css' produces one image pixel per CSS pixel. scale: 'device' uses device pixels and is the documented default, so high-density device scale can make the output larger. Use JPEG quality to trade file size against compression artifacts; do not set it for PNG.
3. Control when navigation and capture happen
A screenshot is only as useful as the page state it captures. Select the navigation wait condition based on the site, then wait for a meaningful selector or application state if the initial document load is not enough.
await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 30000
});
await page.locator('main').waitFor({ state: 'visible', timeout: 10000 });
await page.screenshot({ path: 'ready.png' });
Use the navigation options supported by Playwright for your installed version. Common choices include waiting for the document load, DOM content loaded, or network activity to reach the selected state. Pages with ongoing analytics, polling, or streaming may never become network-idle, so prefer a specific selector or application-ready condition when that better represents completion.
For deterministic capture, also account for content that changes independently of navigation: animations, rotating banners, timestamps, personalized content, and delayed images. If your application owns the page, make its screenshot state stable before capture. Avoid adding arbitrary long sleeps as a substitute for a condition you can observe.
4. Browser, viewport, and context choices
Browser choice, viewport, and device scale affect rendering and output dimensions. Configure the context or page before navigation so the site lays out at the intended size.
const context = await browser.newContext({
viewport: { width: 1280, height: 800 },
deviceScaleFactor: 1
});
const page = await context.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'desktop.png', scale: 'css' });
Use a consistent browser, browser version, operating environment, viewport, and scale when screenshots are compared or used as regression baselines. Playwright’s visual comparison guidance notes rendering can vary with the host OS, browser version, settings, hardware, power source, and headless mode. See its visual comparisons guide for that environment sensitivity.
5. Complete runnable examples in other languages
The title’s method is Playwright. These examples use its Python and Java APIs to perform the same navigation-then-capture flow. Each writes a viewport screenshot to a local file.
Python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
try:
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.goto("https://example.com", wait_until="load", timeout=30000)
page.screenshot(path="screenshot.png")
finally:
browser.close()
Install the Python package and browser using the Playwright Python installation guide. For full-page output, pass full_page=True to page.screenshot().
Java
import com.microsoft.playwright.*;
import java.nio.file.Paths;
public class Capture {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
try {
Page page = browser.newPage();
page.navigate("https://example.com");
page.screenshot(new Page.ScreenshotOptions()
.setPath(Paths.get("screenshot.png")));
} finally {
browser.close();
}
}
}
}
Use the official Playwright Java guide for dependency and project setup. In Java, screenshot options are supplied through Page.ScreenshotOptions; consult the versioned API for the option names available in your installed release.
cURL is not a Playwright replacement
Playwright runs a browser and can render JavaScript-driven pages. A plain HTTP request with cURL downloads a response; it does not execute the page in a browser or produce a rendered screenshot. Use cURL for an HTTP fetch, or call a screenshot service when you need an image without managing a browser process.
6. Screenshot output versus visual regression tests
For an artifact, call page.screenshot(). For a visual regression assertion in Playwright Test, use await expect(page).toHaveScreenshot(). The test runner creates a reference screenshot on the first run and compares later output against it; screenshot assertions wait for consecutive captures to stabilize before comparing. See the Playwright visual comparisons documentation and its screenshot assertion API.
Do not use a baseline created in one rendering environment as if it were guaranteed identical in another. Pin the browser and run comparison jobs in a consistent environment; decide how your tests handle dynamic content and expected changes.
7. Or skip the browser setup
If your application only needs a screenshot for a URL, ScreenshotNeo provides a website screenshot API and MCP server. Make one GET request; the API returns an image or PDF. See the ScreenshotNeo API documentation for request options and formats.
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)
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 request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot; each cleanup step can be turned off. Bot checks, blank pages, and failed loads are never billed, and response headers report the page verdict and billing status. Its MCP server gives AI agents screenshot, page-info, and PDF-capture tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for ScreenshotNeo: 1,000 screenshots a month, no card required.
8. Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
| Browser executable is missing | The package is installed but the browser binary is not. | Install the browser for the Playwright version in your project with its install command; follow the language-specific installation guide. |
| Navigation times out | The page is slow, unreachable, or waiting for an event that does not occur. | Check the URL and network access, set an intentional timeout, and choose a suitable wait condition. For continuously active sites, wait for a meaningful selector instead of network idle. |
| Screenshot is blank or shows an error page | The page did not load the expected content, requires authentication, or returned an error state. | Inspect the response and page state before capture; provide the required context credentials or headers when appropriate, and wait for the actual content selector. |
| Element screenshot cannot find the target | The selector is incorrect, the element is conditional, or it has not appeared yet. | Verify the selector against the rendered page and wait for it to become visible before taking its screenshot. |
| Full-page image is unexpectedly large | The page is tall, and device scale can multiply output pixels. | Capture only a needed element or region, consider scale: 'css', or resize the result downstream. |
| Image type or quality is unexpected | The file extension determines encoding; quality applies to JPEG, not PNG. | Use a matching extension or explicit supported type, and set quality only for JPEG. |
| Visual test changes between runs | Rendering environment or dynamic page content differs. | Keep browser, OS, viewport, scale, and test environment consistent; stabilize animations and content that changes between captures. |
| Browser process remains after an error | Cleanup did not run after a failed operation. | Close the browser in a finally block or use the language’s resource-management construct. |
9. Performance, reliability, and cost
Local Playwright has no per-screenshot service charge, but you operate the browser processes, machines, storage, and maintenance. Browser startup is overhead, so a worker that handles many captures can reuse a browser while isolating jobs with separate contexts. Limit concurrency to the CPU and memory available: each active page consumes resources, and large full-page images add encoding and transfer costs.
For reliability, set navigation and selector timeouts deliberately, close contexts and browsers when finished, and record the target URL and failure stage in your own logs. Retry only transient failures and cap retries; repeated retries cannot fix invalid URLs, blocked access, or a selector that never exists. For visual tests, consistent environments matter as much as the screenshot call itself.
A hosted screenshot API shifts browser operation to a service and may bill according to its plan and request outcomes. ScreenshotNeo’s plans are Free: 1,000 per month; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing gives two months free, and every feature is available on every plan. Check the documentation for API configuration before integrating it.
10. Frequently asked questions
Does Playwright need a visible desktop to take screenshots?
The browser can run headlessly; screenshot capture does not require a user-visible desktop. For visual comparisons, keep the execution mode consistent between baseline and comparison runs.
Can I capture a page that requires a login?
Yes, when you provide the authenticated browser state or perform the login flow before capture. Keep credentials out of source code and follow the site’s access rules.
Should I use page.screenshot() or toHaveScreenshot()?
Use page.screenshot() when your code needs an image artifact. Use toHaveScreenshot() inside Playwright Test when the purpose is to compare rendered output with a baseline.


