How to Write a Playwright Screenshot Script in Python
Install Playwright, capture viewport, full-page, and element screenshots in Python, then troubleshoot timing, browsers, formats, and reliability.
Direct answer: install Playwright and its browser binaries, launch a browser, open a page, call page.screenshot(), and close the browser. The smallest synchronous script is:
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()
By default, this saves the visible viewport. Add full_page=True for the complete scrollable document or call page.locator(".selector").screenshot() for one element. Playwright also provides an asynchronous Python API for applications that already use asyncio.
1. Install Playwright and a browser
Install the Python package, then download the browser binaries:
python -m pip install playwright
playwright install
On Linux CI images, install Chromium and its operating-system dependencies with:
playwright install --with-deps chromium
The official installation documentation lists Python 3.8 or newer and operating-system requirements; check the current requirements before pinning an environment. See the Playwright Python installation guide and browser installation guide.
2. Capture a viewport screenshot
Create screenshot.py with the script below and run python screenshot.py:
from playwright.sync_api import sync_playwright
URL = "https://example.com"
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.goto(URL, wait_until="load")
page.screenshot(path="screenshot.png", type="png")
browser.close()
page.goto() navigates to the URL, and page.screenshot() writes the image. The browser is headless by default. Use headless=False while debugging so you can see the page:
browser = p.chromium.launch(headless=False, slow_mo=250)
Keep the browser in a with sync_playwright() block so Playwright is cleaned up even when the script grows.
3. Capture the full page
Set full_page=True to capture the full scrollable document:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.goto("https://example.com", wait_until="networkidle")
page.screenshot(path="full-page.png", full_page=True)
browser.close()
Full-page capture is different from a tall viewport: Playwright renders the complete scrollable page. Very long pages can require more memory and produce large files, so consider clipping a region or capturing important sections separately.
4. Capture one element
Use a locator when you need a card, header, chart, or other component instead of the whole page:
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.locator("header").screenshot(path="header.png")
browser.close()
Prefer a stable selector such as a test ID or semantic element. A locator screenshot waits for the element to be actionable; it fails if the selector never resolves, which helps reveal incorrect selectors instead of silently producing the wrong image.
5. Use the async Python API
Choose the async API when your surrounding service already runs an asyncio event loop:
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page(viewport={"width": 1440, "height": 900})
await page.goto("https://example.com", wait_until="load")
await page.screenshot(path="screenshot.png")
await browser.close()
asyncio.run(main())
Do not call the synchronous API from inside an already-running event loop. In an async web worker, keep the browser lifecycle outside individual requests when possible and create isolated pages or contexts per job.
6. Wait for the page state you actually need
Navigation finishing does not guarantee that application data, fonts, or images are ready. Select a wait strategy based on the page:
# Wait for a navigation state
page.goto(URL, wait_until="networkidle")
# Wait for a known application marker
page.wait_for_selector("[data-testid='report-ready']")
# Wait for a fixed delay only when the page has no reliable marker
page.wait_for_timeout(1000)
A selector or application-ready signal is usually more stable than an arbitrary delay. For pages with continuously open analytics or chat connections, networkidle may never be reached; wait for the content you need instead.
7. Screenshot options
The Page and Locator screenshot APIs expose options for output, geometry, and visual determinism. Check the version of Playwright installed in your environment because supported options can change.
| Option | Use | Example |
|---|---|---|
path |
Write an image file. | path="shot.png" |
full_page |
Capture the complete scrollable page. | full_page=True |
clip |
Capture a rectangle in page coordinates. | clip={"x":0,"y":0,"width":800,"height":600} |
type |
Select png or jpeg; WebP is supported in recent Playwright releases. |
type="jpeg" |
quality |
Set JPEG/WebP quality where supported. | quality=80 |
scale |
Control CSS-pixel versus device-pixel output size. | scale="css" |
omit_background |
Keep a transparent background where the browser supports it. | omit_background=True |
mask |
Cover dynamic or sensitive locators during capture. | mask=[page.locator(".timestamp")] |
| Animation controls | Disable or control animations for repeatable images. | Use the screenshot API’s animation option for your installed version. |
When post-processing or pixel-diffing, omit path and receive bytes instead:
image_bytes = page.screenshot(type="png")
with open("screenshot.png", "wb") as output:
output.write(image_bytes)
8. Browser, viewport, and device choices
Playwright supports Chromium, Firefox, and WebKit. Select the engine that matches the compatibility question you are investigating:
with sync_playwright() as p:
browser = p.firefox.launch()
page = browser.new_page(viewport={"width": 1280, "height": 800})
page.goto("https://example.com")
page.screenshot(path="firefox.png")
browser.close()
For responsive checks, create pages with different viewport sizes or use Playwright’s device descriptors. A viewport is not the same as a physical display: also set device scale factor, locale, color scheme, timezone, and user agent when those values affect rendering.
9. Make captures deterministic
- Wait for a stable application marker, not just the first navigation event.
- Disable or wait for animations when comparing pixels.
- Mask timestamps, rotating ads, cursors, and other changing regions.
- Use a fixed viewport, locale, timezone, and color scheme.
- Load the same browser engine and pinned dependency versions in CI.
- Use test data that does not change between runs.
These controls reduce visual differences; they do not make a page deterministic if the page itself uses random content or external services.
10. A reusable production-style function
from pathlib import Path
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError
def capture(url: str, output: str, full_page: bool = False) -> None:
Path(output).parent.mkdir(parents=True, exist_ok=True)
with sync_playwright() as p:
browser = p.chromium.launch()
try:
page = browser.new_page(
viewport={"width": 1440, "height": 900},
color_scheme="light",
)
page.goto(url, wait_until="domcontentloaded", timeout=30_000)
page.wait_for_selector("body", state="visible", timeout=10_000)
page.screenshot(path=output, full_page=full_page, type="png")
except PlaywrightTimeoutError as exc:
raise RuntimeError(f"Timed out while capturing {url}") from exc
finally:
browser.close()
if __name__ == "__main__":
capture("https://example.com", "artifacts/example.png", full_page=True)
11. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Executable doesn't exist |
Browser binaries were not installed or are unavailable in the runtime. | Run playwright install, or install the targeted browser with playwright install --with-deps chromium. |
Timeout in goto |
The server is slow, unreachable, redirecting, or waiting on a resource. | Check the URL from the same machine, set an appropriate timeout, and use domcontentloaded plus an explicit readiness selector. |
Timeout in wait_for_selector |
The selector is wrong, the element is inside a frame, or the application never reaches that state. | Inspect the page in headed mode, verify the selector, and target the correct frame. |
| Blank or incomplete image | Content is rendered after the screenshot call or is below a lazy-loading boundary. | Wait for a content marker, scroll or use full-page capture, and wait for images or fonts required by the design. |
| Element screenshot fails | The locator matches nothing, is hidden, or is covered by another element. | Use a stable locator, wait for visibility, and inspect with headless=False. |
| Different pixels in CI | Different browser versions, fonts, viewport, locale, animation, or dynamic data. | Pin the environment, set rendering inputs explicitly, disable animations, and mask dynamic regions. |
| Huge full-page file or memory pressure | The document is very tall or contains large images. | Capture sections or a clipped region, choose JPEG/WebP where appropriate, and avoid unnecessary device-pixel scaling. |
| Transparent output is opaque | The page or image format does not support transparency in the selected configuration. | Use a format and omit_background setting supported by your installed version, and verify the page background. |
12. Performance, reliability, and cost
Launching a browser is expensive compared with taking another page in an existing browser. For a worker that captures many URLs, launch one browser process, create isolated contexts or pages for jobs, and close pages after each capture. Limit concurrency to what the machine can support; too many simultaneous pages increase memory use and make timeouts more likely.
Full-page screenshots, high device scale factors, large images, and WebKit/Firefox runs can take more resources than a small viewport capture. Measure on the environment that will run the script. Save bytes directly when sending images to storage or a diff service instead of creating unnecessary temporary files.
For reliability, record the URL, browser engine, viewport, Playwright version, navigation timing, and exception details. Retry only transient navigation or network failures, with a bounded retry count; repeating a deterministic selector or code error will not fix it. Treat authentication, robots rules, consent flows, and rate limits as properties of the target site that your script must handle explicitly.
Or skip the browser setup
If you need a screenshot endpoint instead of maintaining browser binaries and workers, ScreenshotNeo accepts one GET request and returns PNG, JPEG, WebP, or PDF. The API supports full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets or custom viewports, retina scale, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, caching, signed links, asynchronous jobs, bulk capture, and a usage API. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const fs = require('node:fs/promises');
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Cookie and consent banners are accepted and removed before capture, along with more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and each response reports its result in X-Page-Verdict and X-Billed headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan, and yearly billing gives two months free. Create a free ScreenshotNeo account.
13. FAQ
Does Playwright save PNG or JPEG by default?
PNG is the default. Set type="jpeg" for JPEG output and provide quality where supported. Recent Playwright releases also support WebP screenshots.
Should I use sync or async Playwright?
Use sync for a straightforward script. Use async when the surrounding program already uses asyncio, such as an async web service or job runner.
How do I screenshot a page after login?
Navigate, perform the login steps, wait for a post-login marker, then call page.screenshot(). For repeated runs, persist authenticated browser state according to your application’s security requirements.
Can I screenshot only part of a page?
Yes. Use a locator for an element or clip for a coordinate rectangle. A locator is usually easier to maintain when the page layout changes.
Which browser should I choose?
Choose Chromium, Firefox, or WebKit based on the browser compatibility you need to represent. Run multiple engines when cross-browser differences are the subject of the capture.
Where are the official API details?
The Playwright Python screenshots guide documents screenshot methods and options, and the Page API reference contains the version-specific details.


