How to capture a webpage screenshot in Playwright with Python
Capture a webpage in Playwright with Python, from viewport and full-page screenshots to elements, clips, image bytes, and common fixes.
Use Playwright’s Python API: navigate to a page, then call page.screenshot(path="screenshot.png"). Add full_page=True to capture the full scrollable page. Install Playwright and its browser first; the complete examples below show synchronous and asynchronous scripts, element and clipped captures, image bytes, and the options most useful for reliable output.
1. Install Playwright and its browser
For a new project, create and activate a virtual environment, then install the Python package and Chromium browser:
python -m venv .venv
# macOS or Linux:
source .venv/bin/activate
# Windows PowerShell:
# .venv\Scripts\Activate.ps1
python -m pip install playwright
python -m playwright install chromium
Playwright controls browser binaries it installs. If a browser executable is missing after installing or updating the package, run the install command again so the browser revision matches the installed Playwright version. Chromium is enough for the examples here; install Firefox or WebKit only if your workflow needs them.
2. Capture a viewport screenshot (synchronous Python)
Save this as screenshot.py and run python screenshot.py. It opens Chromium, navigates to the target, writes a PNG, and closes the browser even if navigation or capture raises an exception.
from playwright.sync_api import sync_playwright
URL = "https://example.com"
with sync_playwright() as p:
browser = p.chromium.launch()
try:
page = browser.new_page()
response = page.goto(URL, wait_until="load", timeout=30_000)
if response is not None and response.status >= 400:
raise RuntimeError(f"Navigation returned HTTP {response.status}")
page.screenshot(path="screenshot.png")
finally:
browser.close()
The default capture is the current viewport. page.goto() can return None for navigations that do not produce a normal document response, such as some client-side or data URL cases; code should not assume a response always exists. The status check above is a deliberate policy for this example: remove or change it if you want to save error pages for inspection.
3. Capture the full page, an element, or a rectangle
Full scrollable page
Set full_page=True when the output should include content below the viewport:
page.screenshot(path="full-page.png", full_page=True)
This captures the full scrollable page, rather than stitching only the currently visible viewport. Very long pages can produce large images and use more memory; see the performance notes below.
A single element
Use a locator to capture one component, such as a product card or header:
page.locator(".product-card").screenshot(path="product-card.png")
Locator screenshots wait for the locator’s actionability checks and scroll the matched element into view. If a fixed overlay covers the element, the image can still show that overlay. A scrollable element contributes only the content currently scrolled into view; scroll it to the desired position before capturing if needed. Prefer locators over ElementHandle.screenshot(), which the API reference marks as discouraged.
A rectangular clip
Use clip to capture a rectangle in page coordinates. The rectangle must have positive width and height:
page.screenshot(
path="region.png",
clip={"x": 20, "y": 100, "width": 640, "height": 360},
)
Choose coordinates for the page state and viewport you actually loaded. If you need a component’s bounds, a locator screenshot is usually less fragile than hard-coded coordinates.
4. Use asynchronous Python
In an async application, use async_playwright and await browser, navigation, and screenshot operations. Save this as async_screenshot.py:
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
try:
page = await browser.new_page()
response = await page.goto(
"https://example.com",
wait_until="load",
timeout=30_000,
)
if response is not None and response.status >= 400:
raise RuntimeError(f"Navigation returned HTTP {response.status}")
await page.screenshot(path="screenshot.png", full_page=True)
finally:
await browser.close()
asyncio.run(main())
Do not call the synchronous Playwright API from an async event loop. Use the async API throughout that task. For multiple independent pages, create pages in one browser and manage concurrency explicitly; launching a new browser for every URL adds startup cost and resource use.
5. Save to disk or keep the image in memory
Passing path writes the screenshot to that file. Playwright infers the format from the extension; without a path, the screenshot method returns image bytes instead. This is useful for uploading, hashing, or processing an image without a temporary file:
image_bytes = page.screenshot()
# Example: write the returned bytes later.
with open("screenshot.png", "wb") as output:
output.write(image_bytes)
The async equivalent is image_bytes = await page.screenshot(). When using a path, choose an extension that matches the requested image type and verify the output directory exists and is writable.
6. Screenshot options and configuration
The following Page screenshot options are useful for common capture requirements. Check the API documentation for the exact behavior in the Playwright version installed in your project.
| Option | What it controls | Notes |
|---|---|---|
full_page |
Capture the full scrollable page instead of the viewport. | Defaults to False. Large pages can produce very large images. |
path |
Save to a file. | The file extension determines the format. Omit it to receive bytes. |
type |
Image format: PNG, JPEG, or WebP. | WebP was added in Playwright Python 1.62; confirm your installed version supports it before relying on it. |
quality |
Lossy image quality for JPEG or WebP. | Does not apply to PNG. Lower quality can reduce output size, with a visual trade-off. |
clip |
Capture a rectangular region. | Specify x, y, width, and height; width and height must be positive. |
scale |
Render at CSS-pixel or device-pixel scale. | Use the documented values "css" or "device" for the installed release. Device scale can increase dimensions and file size. |
animations |
Control CSS and Web Animations during capture. | Use the documented setting that matches whether the image should show a stable or animated state. |
caret |
Control whether a text caret appears. | Useful for reducing incidental differences in editor or form screenshots. |
mask, mask_color |
Cover matching locators with a solid color. | Useful for hiding dynamic or sensitive regions in the artifact. Confirm the mask covers every intended element. |
omit_background |
Make the page background transparent. | Not supported for JPEG, which has no alpha channel. |
For a JPEG, for example, use page.screenshot(path="shot.jpg", type="jpeg", quality=80). For transparent PNG output, use page.screenshot(path="shot.png", omit_background=True). Treat the screenshot as potentially sensitive: it can contain page content, account data, or personal information.
7. Wait for the page state you need
Navigation completing does not guarantee that every image, chart, or client-rendered component is ready. Select the least broad wait that represents the state you want:
wait_until="load"waits for the load event, as in the examples.wait_until="domcontentloaded"can be sufficient when the needed content is already in the document and you do not need all load-event resources.- Wait for a meaningful selector when a specific component signals readiness:
page.locator(".results").wait_for(state="visible", timeout=10_000). - Use a short explicit delay only when the page has a known delayed update without a reliable readiness signal:
page.wait_for_timeout(1_000).
Avoid treating network-idle as a universal guarantee: pages with polling or analytics may keep requests active, while a page can be visually incomplete even after network activity subsides. For lazy-loaded content in a full-page capture, check the resulting image; if the site loads sections only as they enter the viewport, scroll through the page before capturing and wait for the relevant content.
8. Run screenshots automatically with pytest
If you already use Playwright’s pytest plugin and its fixtures, the plugin can save screenshots based on test outcomes. The dossier documents --screenshot and --full-page-screenshot; full-page screenshots on failure require screenshot capture to be enabled, while the plugin otherwise captures the viewport by default. For example:
pytest --screenshot only-on-failure --full-page-screenshot
Check the installed plugin’s help and documentation for accepted values and artifact paths, since plugin options and defaults can vary by release. Use an explicit page.screenshot() call when you need a screenshot at a particular point in a test rather than an artifact managed by the plugin.
9. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Executable doesn't exist or browser launch fails |
The Playwright package is installed but its browser binary is not, or versions are mismatched. | Run python -m playwright install chromium in the same environment as the script. |
| Navigation times out | The site is slow, never reaches the selected load condition, or continues background requests. | Set a timeout appropriate to the task, choose a suitable wait_until condition, and wait for a specific visible element if that defines readiness. |
| Image is blank or content is missing | The page has not rendered the needed content yet, rendering depends on interaction, or content is lazy-loaded. | Wait for a meaningful locator, perform the needed interaction, or scroll through lazy content before a full-page capture. |
| Element locator times out | The selector matches no element, the element is hidden, or it is rendered only after another action. | Check the selector and page state, wait for visibility, and handle any consent or navigation step required by the site. |
| Element image includes an overlay | A cookie banner, modal, or fixed widget covers the target. | Dismiss the overlay when appropriate, or capture a locator for the desired component after the page reaches the intended state. |
| Full-page capture omits some lazy content | The page only loads sections when they enter the viewport. | Scroll through the relevant sections and wait for their content before taking the full-page screenshot. |
| Screenshot is unexpectedly large or slow | The page is long, device scale is high, or the image is lossless. | Capture only the needed element or clip, use CSS scale when suitable, or choose JPEG/WebP with a considered quality setting. |
| WebP option is rejected | The installed Playwright Python release predates WebP screenshot support. | Check the package version; WebP support was added in version 1.62. Use PNG/JPEG or upgrade within your project’s compatibility constraints. |
| File is missing after capture | The path is relative to a different working directory, or the destination is not writable. | Print the current working directory, use an absolute path, create the parent directory, and check permissions. |
10. Performance, reliability, and cost
A local Playwright capture has no per-screenshot API charge, but it uses your machine or CI worker’s CPU, memory, browser storage, and time. The main cost drivers are browser startup, page load, capture size, and concurrent pages. Reuse a browser for batches, cap concurrency to fit available memory, and close pages and browsers in finally blocks or context managers. Reuse contexts only when sharing cookies and storage between captures is intentional.
For repeatable output, keep the browser and package versions controlled, use a consistent viewport and device scale, wait for a defined page state, and account for dynamic content such as timestamps, rotating banners, and animations. Set explicit navigation and locator timeouts appropriate to the target. A timeout does not prove the page is broken; it means the requested condition was not reached within the configured interval. For production workflows, record the URL and failure stage, retry only transient failures with a limit, and avoid retrying indefinitely or treating an HTTP error page as a successful capture.
11. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its API returns a screenshot or PDF from one GET request, so you do not need to install or manage a browser for a simple capture. See the ScreenshotNeo API docs.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month, with no card required.
12. Frequently asked questions
Can I capture a page without writing an image file?
Yes. Omit path; the screenshot call returns bytes that you can pass to another function or write later.
Does full_page=True capture a whole scrollable component?
No. It applies to the page screenshot. For a locator screenshot of a scrollable element, only its currently scrolled content is captured.
Can I use the same code for Firefox or WebKit?
The Python API has browser launchers for Chromium, Firefox, and WebKit. Install the browser you intend to launch, then use its launcher in place of p.chromium.
How do I capture screenshots when a test fails?
If you use the Playwright pytest plugin, enable its screenshot option and use --full-page-screenshot when full-page failure artifacts are needed. For a screenshot at a specific point in the test, call the page screenshot API directly.


