Capture a Website Screenshot Only After a Specific Element Appears in Playwright
Wait for a Playwright locator to become visible, then capture the element, viewport, or full page. Includes runnable examples, options, and fixes for common timing problems.
To capture a website screenshot only after a specific element appears, locate the element, wait until it is visible, then take the screenshot. In a Playwright Test test, use the retrying expect(locator).toBeVisible() assertion:
import { test, expect } from '@playwright/test';
test('capture after the panel appears', async ({ page }) => {
await page.goto('https://example.com');
const panel = page.getByRole('region', { name: 'Order summary' });
await expect(panel).toBeVisible();
await panel.screenshot({ path: 'order-summary.png' });
});
Replace the example URL and accessible name with values from your application. The locator screenshot is clipped to the matched element. If you need the viewport or entire page, keep the same wait and use page.screenshot() instead.
1. Choose a locator that identifies the target
A Playwright locator describes how to find an element and is resolved when an action or assertion uses it. Prefer user-facing locators that reflect how a person identifies the control or content: roles and accessible names, labels, or text. When your application exposes a deliberate testing contract, a test ID is also suitable. Avoid long CSS or XPath chains tied to incidental DOM structure.
// Accessible role and name
const panel = page.getByRole('region', { name: 'Order summary' });
// Visible text, if the text uniquely identifies the target
const status = page.getByText('Payment complete', { exact: true });
// Explicit test contract, if configured in the application
const chart = page.getByTestId('revenue-chart');
Make sure the locator identifies the intended element uniquely. If a page contains multiple matching regions or repeated text, refine the role, name, parent scope, or test ID. Do not suppress ambiguity by selecting the first match unless that is genuinely the intended target.
See the official Playwright locator guide for locator strategies and guidance.
2. Wait for the element to become visible
Recommended in Playwright Test: a web-first assertion
Use await expect(locator).toBeVisible() in a Playwright Test test when the element’s visibility is part of the expected page state. The assertion retries until the condition is satisfied or its assertion timeout expires, so it handles elements that appear after navigation without guessing a delay.
import { test, expect } from '@playwright/test';
test('capture a report after it renders', async ({ page }) => {
await page.goto('https://example.com/reports');
const report = page.getByRole('region', { name: 'Monthly report' });
await expect(report).toBeVisible({ timeout: 10_000 });
await report.screenshot({ path: 'monthly-report.png' });
});
Playwright Test’s default timeout for asynchronous expect matchers is 5 seconds. Set a different timeout for a particular assertion when this page state legitimately takes longer, or configure the expect timeout in your test configuration. Avoid increasing timeouts to conceal a locator that never matches or a page that is failing to load.
Explicit locator wait
For standalone code or when you want to express the wait directly, use locator.waitFor():
await report.waitFor({ state: 'visible', timeout: 10_000 });
await report.screenshot({ path: 'monthly-report.png' });
Supported states are attached, detached, visible, and hidden. The default is visible. Choose the state that matches your requirement: attached means present in the DOM, while visible means the element has a non-empty bounding box and is not styled with visibility: hidden. Visibility does not guarantee that the element is unobscured or prominent to a person.
The Playwright Page API and locator API document wait and screenshot behavior. Check the API reference for the Playwright release installed in your project before relying on options that may be version-specific.
3. Capture the right area
After the wait, choose the screenshot method that matches the image you need:
| Code | Captured area | When to use it |
|---|---|---|
await target.screenshot({ path: 'target.png' }) |
The matched element, clipped to its bounds | You need just the component or result |
await page.screenshot({ path: 'viewport.png' }) |
The current viewport | You need the visible browser area |
await page.screenshot({ path: 'full.png', fullPage: true }) |
The full scrollable page | You need the page from top to bottom |
For example, wait for a page section and capture the full page once it appears:
const results = page.getByRole('region', { name: 'Search results' });
await expect(results).toBeVisible();
await page.screenshot({ path: 'search-results-page.png', fullPage: true });
A full-page image includes content outside the viewport. A locator screenshot is limited to the target element. These are different boundaries, so choose based on the artifact you intend to inspect or share.
4. Runnable setup and examples
Playwright Test with TypeScript
Install Playwright Test and its browser binaries using the official installation instructions. Save the following as a test file such as tests/capture.spec.ts and run it with npx playwright test. The example URL and accessible name must correspond to a real page and element in your application.
import { test, expect } from '@playwright/test';
test('save the element after it appears', async ({ page }) => {
await page.goto('https://example.com');
const target = page.getByRole('heading', { name: 'Example Domain' });
await expect(target).toBeVisible();
await target.screenshot({ path: 'heading.png' });
});
Standalone JavaScript with Playwright
For a script outside the test runner, launch a browser, create a page, navigate, wait with the locator API, capture, and close the browser. Install the playwright package and its browser binaries as described in the Playwright documentation.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com');
const target = page.getByRole('heading', { name: 'Example Domain' });
await target.waitFor({ state: 'visible', timeout: 10_000 });
await target.screenshot({ path: 'heading.png' });
} finally {
await browser.close();
}
})();
This script uses CommonJS syntax. In an ES module, import chromium with import { chromium } from 'playwright'; and retain the same flow.
5. Handle dynamic content and screenshot stability
Waiting for visibility answers a specific question: has the target become visible? It does not mean every image, animation, chart, or third-party widget on the page has finished rendering. If the screenshot must reflect a later state, wait for that state using a locator or a specific application signal, such as a completed status or a result value.
- Image or chart content: wait for a meaningful rendered state, not merely the container’s existence, if its contents load afterward.
- Animations: use screenshot options documented for your installed Playwright version when animation affects the image.
- Volatile or sensitive regions: screenshot masking options can cover known dynamic or private content where appropriate.
- Visual regression tests: use
toHaveScreenshot()when the goal is to compare a page or locator against a baseline. It is a test assertion that waits for consecutive screenshots to stabilize for comparison; it is distinct from saving a one-off screenshot withscreenshot().
Consult the Playwright test documentation and the installed-version API reference for the relevant controls. Do not assume that an assertion screenshot and a one-off capture have identical behavior.
6. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot happens before the target appears | The code checked immediately or used a fixed delay that was too short. | Use await expect(target).toBeVisible() or await target.waitFor({ state: 'visible' }) before capture. |
isVisible() returns false even though the element appears later |
isVisible() is an immediate check; it does not wait for a future state. |
Replace it with a retrying assertion or explicit locator wait. |
| Wait succeeds but screenshot is blank or incomplete | The container became visible before its asynchronous contents rendered. | Wait for the actual content or a page-specific completion indicator inside the container. |
| Timeout: locator did not become visible | The selector, accessible name, navigation, or expected page state is wrong; alternatively, the element never becomes visible. | Inspect the rendered page and locator match. Confirm navigation succeeded, refine the locator, and only then adjust the timeout if the real render takes longer. |
| Strict mode or multiple match error | The locator matches more than one element. | Make it unique using a role/name, a more specific accessible name, a scoped parent, or a test ID. Avoid arbitrarily taking the first match. |
| Element is attached but still hidden | The wait uses attached, which checks DOM presence rather than visibility. |
Use state: 'visible' when the screenshot should follow display. |
| Captured image has the wrong boundaries | The code uses an element screenshot when a viewport or full-page screenshot was intended, or vice versa. | Use target.screenshot() for the matched element; use page.screenshot() for the viewport or fullPage: true for the full page. |
| Screenshot looks unstable between runs | Animations, changing data, fonts, ads, or delayed content vary between captures. | Wait for a deterministic application state, control animations where supported, and mask known volatile regions for visual comparison. |
page.waitForSelector() is discouraged for new code in favor of locator-based waits and web-first assertions. See the Playwright best practices. Avoid replacing a condition-based wait with a fixed waitForTimeout(): a sleep neither confirms that the target appeared nor adapts to different load times.
7. Performance, reliability, and cost
A locator wait is condition-based: it proceeds as soon as the expected state appears, instead of always consuming a fixed sleep duration. Keep the locator specific so Playwright can target the intended element, and wait for the smallest meaningful condition that makes the screenshot useful. Very long timeouts can make genuine failures slow to surface; very short ones can fail on legitimate slower page states.
For reliable captures, use a stable target locator, wait for the state that matters, and keep the screenshot scope intentional. A visible container is not proof that all its contents are ready. Browser capture also requires installing and running a browser, so account for browser setup and execution in your own environment. Playwright itself does not bill per screenshot; infrastructure and browser execution costs depend on where and how you run it.
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request captures a URL as PNG, JPEG, WebP, or PDF. The API can wait for a selector, a delay, or network idle, and it offers element capture and full-page capture. See the ScreenshotNeo API documentation for request parameters.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The equivalent Python request:
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)
And Node.js:
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 removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed, and response headers indicate 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 a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo free: 1,000 screenshots a month, no card required.
9. FAQ
Does visible mean the element is clickable?
No. Visibility checks geometry and CSS visibility; they do not establish that an element is enabled, unobscured, or ready for every interaction.
Can I wait for an element to disappear before taking a screenshot?
Yes. Use await locator.waitFor({ state: 'hidden' }) when the desired page state is that the element has become hidden or detached, then capture the page or another target.
Should I use a screenshot assertion for a saved image?
Use screenshot() to write an image file. Use toHaveScreenshot() when the test should compare rendered output with a screenshot baseline.
Can I wait for a CSS selector?
Yes. Create a locator with page.locator('your-selector'), then use the same visibility assertion or locator wait. Prefer a user-facing locator when it can identify the target reliably.


