Blog 5 min read

How to Take Playwright Screenshots: Complete 2026 Guide

Playwright screenshots in Node.js and Python: full page, element capture, viewports, waiting strategies, and the pitfalls (lazy loading, fonts, Docker) with working code.

How to Take Playwright Screenshots: Complete 2026 Guide

Playwright is the most capable browser automation library right now, and screenshots are one of its core features. This guide covers every capture mode with working code in Node.js and Python, the waiting strategies that separate reliable screenshots from flaky ones, and the honest list of pitfalls you will hit when you move from a demo script to production volume.

Basic screenshot

The minimal version in Node.js:

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
await browser.close();

And Python:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://example.com")
    page.screenshot(path="screenshot.png")
    browser.close()

Both produce a PNG of the current viewport (1280x720 by default). Run npx playwright install chromium or playwright install chromium first to download the managed browser build.

Or skip the setup entirely

Everything in this guide is one GET request with SnapRender. No browser to babysit, no timeouts to tune.

200 screenshots a month free. First render in under a minute.

Full-page screenshots

One option flag captures the entire scrollable page:

await page.screenshot({ path: 'full.png', fullPage: true });
page.screenshot(path="full.png", full_page=True)

This works until the target page lazy-loads content, which most modern pages do. Playwright scrolls to stitch the full page, but images that load on scroll often have not arrived by the time their section is captured, so you get gray placeholders. The fix is to walk the page before capturing:

// Scroll through the page to trigger lazy-loaded images
await page.evaluate(async () => {
  await new Promise((resolve) => {
    let total = 0;
    const timer = setInterval(() => {
      window.scrollBy(0, 600);
      total += 600;
      if (total >= document.body.scrollHeight) {
        clearInterval(timer);
        window.scrollTo(0, 0);
        resolve();
      }
    }, 100);
  });
});
await page.waitForLoadState('networkidle');
await page.screenshot({ path: 'full.png', fullPage: true });

Element screenshots

Capture a single component instead of the page:

await page.locator('.pricing-table').screenshot({ path: 'pricing.png' });
page.locator(".pricing-table").screenshot(path="pricing.png")

Playwright scrolls the element into view and waits for it to be stable, visible, and attached. This is the cleanest way to capture charts, cards, or tables for reports.

Viewports, devices, and retina output

const context = await browser.newContext({
  viewport: { width: 390, height: 844 },
  deviceScaleFactor: 3,
  userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) ...',
});

Or use the built-in device registry:

import { devices } from 'playwright';
const context = await browser.newContext({ ...devices['iPhone 15 Pro'] });

deviceScaleFactor: 2 doubles the pixel density for crisp retina output: a 1280x720 viewport produces a 2560x1440 image.

Waiting: where screenshot reliability lives

Most "Playwright screenshot is blank or half-rendered" problems are waiting problems. The tools, in order of preference:

// 1. Wait for a specific element that signals the page is ready
await page.waitForSelector('.article-body', { state: 'visible' });

// 2. Wait for network traffic to settle (SPAs, dynamic content)
await page.waitForLoadState('networkidle');

// 3. Wait for web fonts so text does not capture in fallback font
await page.evaluate(() => document.fonts.ready);

// 4. Fixed delay: last resort for animations and carousels
await page.waitForTimeout(1000);

Prefer selector waits over network idle, and network idle over fixed delays. Analytics beacons can keep the network from ever going idle, so cap it with a timeout. Disable CSS animations if pixel-stable output matters:

await page.emulateMedia({ reducedMotion: 'reduce' });

The production pain list

Everything above works in a local script. Production is where the real work starts. Plan for each of these before you commit to self-hosting screenshots:

Lazy-loaded images. Covered above, but it breaks per-site: some pages virtualize content and never render off-screen sections at all.

Web fonts. Without document.fonts.ready, captures race the font download and text renders in Times New Roman. Icon fonts show boxes.

Cookie and consent banners. In the EU nearly every commercial page loads with an overlay. You either click through per-site selectors, inject CSS to hide known containers, or your screenshots all have banners in them. Keeping those selector lists current is an ongoing maintenance job. (Here is how the pros handle cookie banner removal.)

Docker and CI. Chromium needs dozens of system libraries. Use the official Playwright image (large, but it works) or maintain the dependency list yourself. GitHub Actions example:

jobs:
  screenshots:
    runs-on: ubuntu-latest
    container: mcr.microsoft.com/playwright:v1.45.0-jammy
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: node capture.js

Memory and scale. Each browser instance holds 200-500 MB. Contexts leak if you do not close them in finally blocks. Concurrent captures need a pool with queue management, and a hung page will happily block that pool until you add per-capture timeouts. Self-hosting screenshot infrastructure is a real service you now operate: read the real cost of self-hosting screenshots before scaling this to thousands of captures a day.

Bot detection. Some sites serve headless browsers a challenge page instead of content. You will learn more about user agents, TLS fingerprints, and stealth patches than you planned to.

The managed alternative

Everything in this guide, including the pain list, is one HTTP request against a screenshot API:

curl "https://app.snap-render.com/v1/screenshot?url=https://example.com&full_page=true&block_ads=true&block_cookie_banners=true&device=iphone-15" \
  -H "X-API-Key: YOUR_API_KEY" \
  --output screenshot.png

Full-page stitching, lazy-load handling, font waiting, cookie banner removal, device emulation, and retina output are parameters instead of code you maintain. The free tier covers 200 renders a month with every feature, which is enough to compare output quality against your Playwright setup on your own URLs. Or try it with zero setup in the free website screenshot tool.

Playwright remains the right choice when you need multi-step automation before the capture: logging in, filling forms, navigating flows. For "turn this URL into an image, reliably, at volume," a managed API is the version of this guide you do not have to debug.

For the same walkthrough with Puppeteer, see the Puppeteer screenshot guide. Python-first developers can compare approaches in how to screenshot a website with Python.

Frequently asked questions

How do I take a full-page screenshot in Playwright?

Pass fullPage: true (Node.js) or full_page=True (Python) to page.screenshot(). Playwright scrolls and stitches the whole page. Trigger lazy-loaded images first by scrolling to the bottom and back, or parts of the page will be blank.

Why are images missing from my Playwright screenshots?

Almost always lazy loading: images below the viewport have not loaded when the capture fires. Scroll through the page first, wait for network idle, then screenshot. Web fonts have the same issue and need document.fonts.ready.

Does Playwright need a real browser installed?

Yes. npx playwright install downloads managed browser builds (Chromium, Firefox, WebKit), roughly 150 MB or more per browser plus system dependencies. In Docker you need the official Playwright base image or a long apt-get list.

Can I take screenshots without running Playwright myself?

Yes. A screenshot API runs the browsers server-side, so capturing becomes one HTTP request with no installs, no browser updates, and no memory management. That trade is usually worth it in production once volume or reliability starts to matter.

Or skip the setup entirely

200 screenshots a month free. First render in under a minute.

Grab a free API key