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:
- Wait for network idle (recommended for most cases)
- Wait for specific elements (when you know what matters)
- 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_playwrightwith 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 elementspage.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 asynciofrom playwright.async_api import async_playwrightasync 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:
-
Browser Management: Chromium uses 200-500MB per instance. Running 10 concurrent screenshots needs 5GB RAM minimum, plus CPU for rendering.
-
Deployment Complexity: Playwright requires specific system dependencies. Docker images balloon to 1.5GB. Lambda requires custom layers.
-
Maintenance Burden: Browsers update constantly. Playwright versions break. Sites change their loading behavior.
-
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:
Consideration | DIY Playwright | Supacrawler API |
---|---|---|
Setup time | Hours to days | Minutes |
Browser updates | Your problem | Handled automatically |
Scaling | Provision servers | Automatic |
Memory management | Manual tuning | Built-in |
Maintenance | Ongoing | None |
Start with 1,000 free screenshots • View API docs
What You've Learned
Screenshot automation comes down to three things:
- Timing: Wait for the right moment (network idle or specific elements)
- Capture options: Full page, mobile devices, or specific elements
- 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.