Playwright is the most popular browser automation tool right now. Multi-browser support, built-in auto-waiting, SDKs for four languages. Developers who already use Playwright for testing naturally reach for page.screenshot() when they need to generate images. That works fine in a test suite. It works less fine when screenshots become a production feature — link previews, PDF reports, social cards, compliance archives — and you discover that the method was built for testing, not for image generation at scale.
This comparison covers what screenshot() can do, what it can't, how a dedicated API handles the gaps. I'm behind screenshotrun, so keep that in mind. The Playwright limitations below are documented in their GitHub issue tracker — I'll link the specific issues so you can check yourself.
For the infrastructure side (Docker images, memory limits, scaling headless browsers), see our Playwright vs screenshot API comparison. This page focuses on the product — what the screenshot method gives you and where it stops.
What screenshot() gives you
The full parameter list for page.screenshot() as of late 2026:
| Parameter | What it does |
|---|---|
type |
Output format: PNG, JPEG, or WebP |
quality |
Compression quality (JPEG/WebP only, ignored for PNG) |
fullPage |
Capture entire scrollable area, not just viewport |
clip |
Crop to a specific rectangle (x, y, width, height) |
omitBackground |
Transparent background for PNG/WebP |
mask |
Cover specified elements with solid boxes |
scale |
CSS or device pixel density |
animations |
Disable CSS animations and transitions |
style |
Inject CSS that applies only during capture |
timeout |
Maximum wait time in milliseconds |
Good enough for test screenshots. But what about everything a production screenshot workflow needs?
What's missing
No AVIF output. No TIFF. No PDF generation from the screenshot method — Playwright has a separate page.pdf(), but that only works in headless Chromium. Try it in Firefox or WebKit and it throws. No output resizing or thumbnails. What about blocking ads, chat widgets, cookie banners? No stealth mode. No metadata extraction. No webhooks. No caching. No certified timestamps. No scroll video.
Each gap means custom code. Need cookie banners gone? Write selectors for every consent framework — OneTrust, Cookiebot, TrustArc, whatever the site uses. Need lazy-loaded images to actually appear? Playwright's fullPage: true doesn't scroll the page before capture. It reads scrollHeight and grabs a tall viewport — lazy images below the fold stay gray (GitHub issue #40941, still open). The fix is a bunch of scroll-and-wait code that you end up writing, debugging, then maintaining forever.
Problems that keep coming back
Beyond the missing parameters, there are deeper issues that developers run into once they try to use Playwright screenshots in production consistently.
Fonts look different everywhere
Screenshot on your Mac. Same screenshot in your CI pipeline's Ubuntu container. Do they match? Almost never. Different anti-aliasing, different sub-pixel positioning, sometimes completely different fallback fonts when the custom font file isn't installed. Playwright's own docs recommend Docker for consistent results.
Docker helps, but the official Playwright image is about 2 GB. Custom fonts need manual installation. And even inside Docker, variable fonts still render inconsistently (GitHub issues #29596, #31083). One developer spent over a year fighting flaky screenshots — almost entirely font rendering edge cases.
Full-page capture breaks on modern layouts
fullPage: true assumes the scrollable element is <body>. Modern SPAs often scroll inside a container div — fixed header on top, scrollable main area, maybe a fixed sidebar. What happens when the scroll container isn't <body>? Playwright captures only the viewport height and cuts off everything below (GitHub issue #12962). The only workaround is resizing the viewport to match the container's scroll height, which means knowing the container's selector beforehand.
Bot-protected sites block you
Playwright has no stealth mode built in. Sites running Cloudflare, DataDome, PerimeterX detect the headless browser and serve blocks, CAPTCHAs, or stripped-down pages. The third-party playwright-stealth plugin patches some fingerprint checks, but doesn't touch IP reputation, TLS fingerprinting, behavioral analysis. For your own staging server this doesn't matter. For capturing third-party pages in production it matters a lot.
40 lines vs 12 lines
A typical production task: screenshot a page, block cookie banners, load lazy content, resize to 800px wide, deliver as WebP.
With Playwright you need about 40 lines — launching the browser, navigating, removing cookie elements with fragile per-site selectors, scrolling to trigger lazy images, waiting, capturing, then resizing with Pillow because Playwright has no resize parameter:
# Playwright: ~40 lines
from playwright.sync_api import sync_playwright
import time
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 1280, "height": 720})
page.goto("https://example.com/page", wait_until="networkidle")
# Remove cookie banners (fragile, breaks when sites update)
for selector in [
"#onetrust-banner-sdk", ".cookie-consent",
"[data-testid='cookie-banner']", "#CybotCookiebotDialog"
]:
try:
page.locator(selector).evaluate("el => el.remove()")
except:
pass
# Scroll to trigger lazy loading
total_height = page.evaluate("document.body.scrollHeight")
current = 0
while current < total_height:
page.evaluate(f"window.scrollTo(0, {current})")
time.sleep(0.3)
current += 720
total_height = page.evaluate("document.body.scrollHeight")
page.evaluate("window.scrollTo(0, 0)")
time.sleep(1)
screenshot = page.screenshot(full_page=True, type="webp", quality=85)
# Resize — Playwright can't, need Pillow
from PIL import Image
from io import BytesIO
img = Image.open(BytesIO(screenshot))
ratio = 800 / img.width
img = img.resize((800, int(img.height * ratio)))
img.save("output.webp", "WebP", quality=85)
browser.close()
Same result with a screenshot API:
# screenshotrun: 12 lines
import requests
response = requests.get(
"https://api.screenshotrun.com/v1/screenshots/capture",
params={
"url": "https://example.com/page",
"format": "webp",
"quality": 85,
"full_page": "true",
"block_cookies": "true",
"resize_width": 800
},
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
with open("output.webp", "wb") as f:
f.write(response.content)
The Playwright version has fragile cookie selectors that break when sites update their consent banners. The scroll loop is timing-dependent — too fast and images don't load, too slow and the script takes ages. The API version handles all of this server-side. Same output, fraction of the code.
Side-by-side comparison
| Capability | Playwright screenshot() | screenshotrun API |
|---|---|---|
| Output formats | PNG, JPEG, WebP | PNG, JPEG, WebP, AVIF, TIFF, PDF |
| Lazy content | Manual scroll code | Built-in with full_page |
| Cookie/ad/chat blocking | Manual selectors per site | block_cookies, block_ads, block_chats |
| Output resize | Not available (need Pillow/Sharp) | resize_width, resize_height |
| Stealth mode | Third-party plugin, limited | Built-in stealth param |
| Dark mode | emulateMedia() call |
dark_mode param |
| Geolocation (IP level) | Navigator override only | geolocation + proxy |
| Metadata extraction | Not available | extract_metadata |
| Certified timestamps | Not available | certified param |
| Scroll video | WebM recording only, no scroll control | MP4/GIF with scroll_video, scroll_speed |
| PDF from any URL | Chromium headless only | format=pdf |
| Webhook callback | Not available | webhook_url |
| Caching | Not available | cache_ttl |
| CSS/JS injection | Separate API calls | css, js in same request |
| HTML/Markdown to image | Not available | html, markdown params |
When to keep Playwright
If your screenshots are part of a test suite — visual regression checks against your own application in CI — Playwright is the right tool. It was designed for that. Bot detection isn't a problem because you're hitting your own staging. Font consistency is handled by your CI Docker image. Lazy loading works predictably because you control the page.
Same goes for one-off local scripts. Quick screen grabs during development, debugging visual bugs, screenshots for a bug report. No infrastructure overhead when you're running it on your laptop for five minutes.
The calculus changes when screenshots become a production feature. You're capturing third-party pages you don't control. You need formats or blocking features that Playwright doesn't have. Volume is high enough that managing a browser fleet becomes its own engineering project. At that point, does it still make sense to maintain all that plumbing yourself?
For the full infrastructure comparison — memory, Docker, scaling — see our Playwright vs screenshot API page. The feature list covers every API parameter. Free tier gives you 100 screenshots per month to compare output quality side by side.
FAQ#
Playwright runs locally alongside your test suite, so you can screenshot pages mid-interaction — after clicking buttons, filling forms, navigating through flows. That tight coupling with browser automation is its strength. A screenshot API takes a URL and returns an image, which covers most production use cases but doesn't replace Playwright for interactive test scenarios.
Playwright's fullPage option measures the page's scrollHeight and captures a tall viewport in one shot. It doesn't scroll the page first. Images using loading="lazy" or IntersectionObserver only load when scrolled into view, so they remain as gray placeholders. This is GitHub issue #40941. The workaround is manually scrolling the page with JavaScript before capturing.
Technically yes, but Playwright bundles system libraries that increase cold start times and push against payload size limits on platforms like AWS Lambda or Vercel Functions. The official Docker image is about 2 GB. A screenshot API avoids this entirely — one HTTP call from any serverless function, no browser binary needed.
The full_page parameter automatically scrolls the page to trigger lazy-loaded images before capturing. It also handles Intersection Observer content, forces visibility on scroll-animated elements, and waits for images to finish loading. No custom scroll code needed.
Playwright's screenshot method supports PNG, JPEG, and WebP. AVIF has bugs on Windows (GitHub issue #37708) and isn't supported in WebKit. PDF is available through a separate page.pdf() method, but only works in headless Chromium — Firefox and WebKit throw errors. screenshotrun supports all six formats (PNG, JPEG, WebP, AVIF, TIFF, PDF) from the same endpoint.