How to Take a Screenshot of a Specific HTML Element with Playwright in Python
Use Playwright’s locator.screenshot() to capture one HTML element. See runnable sync and async examples, options, troubleshooting, and a no-browser API alternative.
To screenshot one HTML element with Playwright in Python, find it with a locator and call locator.screenshot(). For example, page.locator(".header").screenshot(path="header.png") saves a screenshot clipped to that element. Playwright scrolls the element into view and waits for actionability checks before capture. Playwright’s screenshot guide and Locator API reference document the method and its options.
Runnable synchronous example
Install Playwright and its Chromium browser, then run this script. Replace .header with a selector that identifies the element you want.
python -m pip install playwright
python -m playwright install chromium
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", wait_until="domcontentloaded")
header = page.locator(".header")
header.screenshot(path="header.png")
browser.close()
The locator screenshot API was added in Playwright 1.14. For new code, use Locator.screenshot(); the API reference discourages the older ElementHandle.screenshot() approach.
Choose a reliable locator
A locator identifies the target at the time Playwright acts on it, and locators are central to Playwright’s auto-waiting and retry behavior. Use a selector or semantic locator that uniquely identifies the intended element. If the page has repeated matches, narrow the locator instead of relying on whichever match happens to be first.
# CSS selector
card = page.locator(".product-card.featured")
# Accessible role and name
link = page.get_by_role("link", name="Read more")
# Narrow a repeated set by index only when the ordering is meaningful
first_card = page.locator(".product-card").nth(0)
If you need to check how many elements match before capture, use locator.count(). For a page whose content is dynamic, first wait for a meaningful state such as the target becoming visible or application data appearing:
target = page.locator("#receipt")
target.wait_for(state="visible")
target.screenshot(path="receipt.png")
Actionability waiting is not a guarantee that every asynchronous application task has finished. Wait for the application-specific condition your image depends on.
Async version
Use the async API when the surrounding program already uses asynchronous Playwright. Do not mix sync and async calls in the same flow.
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()
await page.goto("https://example.com", wait_until="domcontentloaded")
target = page.locator(".header")
await target.screenshot(path="header.png")
await browser.close()
asyncio.run(main())
Save to a file or use image bytes
Pass path to write an image. A relative path is resolved from the current working directory. The extension determines the image format; the documented formats are PNG, JPEG, and WebP. If you omit path, the method returns image bytes for further processing or storage.
# Save directly
locator.screenshot(path="element.webp")
# Keep the image in memory
image_bytes = locator.screenshot()
with open("element.png", "wb") as image_file:
image_file.write(image_bytes)
For JPEG or WebP, use the quality option to control output quality. It does not apply to PNG. Choose the extension to match the format you want; do not save JPEG bytes under a PNG filename.
Screenshot options
| Option | Use | Details |
|---|---|---|
timeout |
Set the maximum wait for the screenshot operation. | The documented default is 30,000 ms. You can set it for the call or through page or context default timeouts. Zero disables the timeout. |
animations="disabled" |
Capture without ongoing animations or transitions. | Finite animations are fast-forwarded to completion; infinite animations are canceled for capture and resumed afterward. |
caret="hide" |
Avoid a blinking text cursor in the image. | This is the default. Use caret="initial" to leave caret behavior unchanged. |
mask=[locator, ...] |
Cover matching regions, such as variable or sensitive content. | The default mask is pink; set mask_color to change it. Matching invisible elements are masked too. |
omit_background=True |
Capture with a transparent background where supported. | Not applicable to JPEG. |
scale="css" |
Use one image pixel per CSS pixel. | The default, "device", uses device pixels and may produce a larger high-DPI image. |
style |
Apply temporary CSS for the screenshot. | Accepts stylesheet text and pierces Shadow DOM and inner frames. Use when changing presentation is appropriate for the capture. |
quality |
Set JPEG or WebP quality. | Does not apply to PNG. |
Options can be combined. This example disables animation, masks a changing value, and writes a WebP file:
target.screenshot(
path="summary.webp",
animations="disabled",
mask=[page.locator(".account-number")],
mask_color="#333333",
scale="css",
quality=85,
)
What gets captured—and what does not
- The screenshot is clipped to the matched element’s size and position.
- Playwright scrolls the element into view after actionability checks.
- If the element is detached from the DOM during capture, the operation errors.
- An overlay can cover the target. A screenshot does not make obscured content visible.
- For a scrollable container, the image shows only the content at its current scroll position, not the entire scrollable contents.
- Capture can still happen before application data, fonts, or other asynchronous content reaches the state you expect. Wait for that state explicitly when it matters.
If the target is a scrollable panel and you need content below its current viewport, scroll that panel and capture the needed section separately, or choose a page-level approach suited to the whole content. An element screenshot is not a request to expand the element’s internal scrolling area.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot times out | The locator does not resolve to an actionable visible target, or the page has not reached the required state. | Check the selector and page state, wait for the target or required application condition, and adjust the operation timeout only if the page legitimately needs more time. |
| Element is detached | The page replaced or removed the matched node during capture. | Wait for the final UI state and take the screenshot from a locator after the update. Avoid retaining an element handle across rerenders. |
| Wrong or repeated element captured | The locator matches multiple elements or is too broad. | Use a more specific CSS selector or semantic locator; inspect the match count and narrow the target. |
| Image contains an overlay or popup | Another element covers the target. | Wait for the overlay to close, dismiss it where appropriate, or use screenshot styling only if changing the presentation is acceptable. |
| Only part of a panel appears | The target has its own scrollable area. | Scroll the container to the content you need before capture. Locator screenshots include only its currently scrolled content. |
| Blank or stale content appears | The capture ran before the application populated the target. | Wait for a specific selector, text, or application condition rather than assuming navigation completion means all data is ready. |
| Output looks too large or too small | Device pixel scaling differs from the desired output dimensions. | Set scale="css" for CSS-pixel output, or keep "device" for device-pixel output. |
| Transparent output fails or has a solid background | Transparency is incompatible with JPEG or the page background remains opaque. | Use PNG or WebP with omit_background=True, and ensure the page content itself does not paint an opaque background. |
Performance, reliability, and cost
Capturing a single locator avoids writing code to crop a full-page image afterward, but the browser still has to load and render the page. Keep the target locator specific, avoid capturing before the required state is ready, and select an output format and scale appropriate to downstream use. High-DPI device scaling can create larger files than CSS scaling.
For repeatable automation, make the capture condition explicit, disable animation when motion creates variability, and mask dynamic regions when their exact contents do not matter. A timeout limits how long the operation waits; disabling the timeout can leave a stuck capture waiting indefinitely. Screenshot output is generated by your local browser, so account for browser installation and execution in your own environment.
Playwright is an open-source browser automation framework; the cited API documentation does not specify a per-screenshot charge. Your operational costs depend on the machine or service running the browser and the storage or processing you add around the resulting file. No benchmark is implied here.
Or skip the browser setup
If you want an element capture without managing a local Playwright browser, ScreenshotNeo accepts a CSS selector for an element through its screenshot API. The selector parameter is selector. See the ScreenshotNeo API documentation for the supported request parameters.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
--data-urlencode selector=.header \
-o header.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={
"access_key": "YOUR_API_KEY",
"url": "https://stripe.com",
"selector": ".header",
},
timeout=90,
)
r.raise_for_status()
open("header.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com',
selector: '.header',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('header.webp', res);
ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed; each response includes page-verdict and billing headers. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo’s free plan to try 1,000 screenshots a month with no card.
FAQ
Can I screenshot an element by accessible role?
Yes. Create a locator with a semantic method such as page.get_by_role("link", name="Read more"), then call its screenshot() method.
Does locator.screenshot() return a path?
No. With a path, it saves the image there; without one, it returns image bytes.
Can I capture an element inside an iframe?
Use a locator scoped to the relevant frame, then screenshot that locator. Playwright’s screenshot styling option also applies through inner frames.
Can I use this for a full-page screenshot?
locator.screenshot() is for the matched element’s visible bounds. Use the page screenshot API with its full-page option when your target is the whole document.


