Back to Blog

How to Take Screenshots using Playwright: A Complete Guide

Every web developer eventually needs to take screenshots programmatically. Whether you're building visual regression tests, monitoring website changes, or generating social media previews, the fundamental problem is the same: how do you reliably capture what a browser renders?

The challenge isn't just taking a picture. Modern websites load asynchronously, render animations, and change based on user interaction. A screenshot tool must wait for the right moment, handle different viewport sizes, and work consistently across deployments.

This guide starts from first principles: what makes screenshot automation hard, and how do we solve it?

The Core Problem: Timing is Everything

The fundamental challenge in screenshot automation isn't the capture itself—it's knowing when to capture. A webpage isn't "ready" the moment the HTML loads. JavaScript executes, images download, fonts render, and APIs return data. Capture too early, and you get a half-loaded page. Wait too long, and you waste time.

This timing problem has three solutions:

  1. Wait for network idle (recommended for most cases)
  2. Wait for specific elements (when you know what matters)
  3. Use fixed delays (when nothing else works)

Starting Simple: Basic Screenshot with Playwright

Playwright solves the core problem by controlling a real browser and exposing wait strategies. Here's the minimal implementation:

from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://example.com", wait_until="networkidle")
page.screenshot(path="screenshot.png")
browser.close()

This works because networkidle waits until there are no more than 2 network connections for at least 500ms. It's not perfect, but it handles 80% of cases.

Beyond Basic: The Three Critical Features

Once you have basic screenshots working, production use requires three capabilities:

1. Full-Page Capture

By default, screenshots capture only the visible viewport. Full-page screenshots scroll and stitch:

page.screenshot(path="full-page.png", full_page=True)

2. Mobile Emulation

Different devices render differently. Playwright includes device presets:

iphone = p.devices['iPhone 12']
context = browser.new_context(**iphone)
page = context.new_page()
page.goto(url)
page.screenshot(path="mobile.png")

3. Element-Specific Screenshots

Sometimes you only need one component:

element = page.locator("main")
element.screenshot(path="main-only.png")

The Three Problems You'll Actually Face

Theory is clean, but production screenshot automation reveals three consistent problems:

Problem 1: Dynamic Content Never Finishes Loading

Some sites continuously poll for updates. networkidle never triggers. The solution is element-based waiting:

page.goto(url)
page.wait_for_selector(".main-content", state="visible")
page.screenshot(path="screenshot.png")

You wait for what matters, not for everything.

Problem 2: Unwanted Elements Clutter the Screenshot

Cookie banners, chat widgets, and sticky headers pollute screenshots. Hide them before capture:

# Hide unwanted elements
page.evaluate("""
document.querySelectorAll('.cookie-banner, .chat-widget').forEach(el => {
el.style.display = 'none';
});
""")
page.screenshot(path="clean.png")

Problem 3: Taking Multiple Screenshots is Slow

Sequential processing wastes time. Playwright supports concurrent pages:

import asyncio
from playwright.async_api import async_playwright
async def screenshot_url(url, browser):
page = await browser.new_page()
await page.goto(url)
await page.screenshot(path=f"{url.split('//')[-1]}.png")
await page.close()
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
await asyncio.gather(*[
screenshot_url(url, browser) for url in urls
])
await browser.close()

When DIY Becomes Expensive

The code above works. You can take screenshots, handle edge cases, and even parallelize. But production reveals hidden costs:

  1. Browser Management: Chromium uses 200-500MB per instance. Running 10 concurrent screenshots needs 5GB RAM minimum, plus CPU for rendering.

  2. Deployment Complexity: Playwright requires specific system dependencies. Docker images balloon to 1.5GB. Lambda requires custom layers.

  3. Maintenance Burden: Browsers update constantly. Playwright versions break. Sites change their loading behavior.

  4. Reliability: Browser crashes, network timeouts, and memory leaks require sophisticated retry logic and monitoring.

You're not just maintaining screenshot code—you're operating a browser farm.

The Managed Alternative

Supacrawler removes the infrastructure burden. The same screenshot functionality, delivered as an API:

curl -G https://api.supacrawler.com/api/v1/screenshots \
-H "Authorization: Bearer YOUR_API_KEY" \
-d url="https://example.com" \
-d full_page=true \
-o screenshot.png

The difference is operational:

ConsiderationDIY PlaywrightSupacrawler API
Setup timeHours to daysMinutes
Browser updatesYour problemHandled automatically
ScalingProvision serversAutomatic
Memory managementManual tuningBuilt-in
MaintenanceOngoingNone

Start with 1,000 free screenshotsView API docs

What You've Learned

Screenshot automation comes down to three things:

  1. Timing: Wait for the right moment (network idle or specific elements)
  2. Capture options: Full page, mobile devices, or specific elements
  3. Production reality: Managing browsers is harder than taking screenshots

Start with Playwright to understand the fundamentals. Move to managed services when infrastructure becomes a distraction from building your product.

The browser automation works the same way. The difference is who manages it.

By Supacrawler Team
Published on October 3, 2025