ScreenshotNeo

BlogHow-to

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.

By the ScreenshotNeo team4 October 20269 min read

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.