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.
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.