Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Wait for the UI state your screenshot actually needs—not an arbitrary delay. In Playwright, that usually means asserting that expected text or a locator is visible, then capturing the page or element. For visual regression, use toHaveScreenshot(); it waits for two consecutive captures to stabilize before comparing them. A navigation load event can help during navigation, but it does not prove that client-rendered data, lazy images, or application state is ready.

The reliable waiting pattern

A screenshot is ready when the pixels you care about are ready. Identify that prerequisite first:

  • A results page: assert the results heading or a known result row.
  • A dashboard: assert the loaded status and a key data value.
  • A component: wait for the component to be visible, then assert its meaningful text.
  • A visual test: use Playwright Test’s screenshot assertion so Playwright checks pixel stability.

Web-first assertions retry until their condition is met. Locator waits can target visible, hidden, attached, or detached, but visibility alone does not certify that nested images, fonts, or asynchronous data have finished rendering. See the locators guide and Locator API.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Example: wait for the application result

import { test, expect } from '@playwright/test';

test('captures the results state', async ({ page }) => {
  await page.goto('https://example.com/search');
  await page.getByRole('textbox', { name: 'Search' }).fill('playwright');
  await page.getByRole('button', { name: 'Search' }).click();

  await expect(page.getByRole('heading', { name: 'Results' })).toBeVisible();
  await expect(page.getByTestId('result-count')).toHaveText(/d+ results/);

  await page.screenshot({ path: 'results.png', fullPage: true });
});

Replace the locators and expected state with conditions that describe your application. The example is a pattern, not a claim about a particular site’s markup.

#1 Best Overall
Sale
Philips 24 Inch Computer Monitor FHD 100Hz VA VESA Flicker-Free, 241V8LB
  • CRISP CLARITY: This 23.8″ Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
  • INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
  • THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors
  • WORK SEAMLESSLY: This sleek monitor is virtually bezel-free on three sides, so the screen looks even bigger for the viewer. This minimalistic design also allows for seamless multi-monitor setups that enhance your workflow and boost productivity
  • A BETTER READING EXPERIENCE: For busy office workers, EasyRead mode provides a more paper-like experience for when viewing lengthy documents

page.screenshot() versus toHaveScreenshot()

Use a direct screenshot for an artifact

page.screenshot() returns an image buffer or writes a file. A locator’s screenshot() captures only that element, scrolling it into view and performing actionability checks first. If the locator detaches while Playwright is capturing, the operation throws. Neither method, by itself, is documented as running the visual assertion’s two-consecutive-capture stability loop.

await page.screenshot({ path: 'page.png', fullPage: true });
await page.locator('#invoice').screenshot({ path: 'invoice.png' });

Use a screenshot assertion for visual regression

With the Playwright Test runner, expect(page).toHaveScreenshot() and expect(locator).toHaveScreenshot() wait until two consecutive screenshots produce the same result, then compare the final image with the expected snapshot. This behavior is described in the PageAssertions API and LocatorAssertions API.

import { test, expect } from '@playwright/test';

test('homepage has the expected visual state', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
  await expect(page).toHaveScreenshot('homepage.png', { fullPage: true });
});

These screenshot assertions require the Playwright Test runner. If you are using a library-only script, use direct screenshots and explicit application-state assertions instead.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choosing the right wait

Need Use What it establishes Important limitation
Element reaches a DOM or visibility state locator.waitFor({ state }) The locator is attached, visible, hidden, or detached as requested It does not prove that data, images, fonts, or animations are complete.
Specific UI outcome Web-first assertion such as toBeVisible() or toHaveText() The semantic condition, with retry behavior The assertion must express the actual screenshot prerequisite.
Document navigation lifecycle page.waitForLoadState('domcontentloaded') or 'load' The selected navigation event occurred Usually unnecessary before actions because Playwright auto-waits; it may not represent app readiness.
Visual regression toHaveScreenshot() Stable consecutive captures before comparison Requires Playwright Test.
Network silence networkidle No network connections for at least 500 ms Playwright marks it discouraged for tests; it is not a UI-readiness contract.

Why networkidle is usually the wrong answer

The Page API defines networkidle as at least 500 ms with no network connections and says: “Don’t use this method for testing, rely on web assertions to assess readiness instead.” Applications can continue rendering after requests quiet down, while analytics, polling, WebSockets, or other background traffic can prevent the state from ever becoming quiet. Use a load state when you specifically need a navigation lifecycle event, not as a substitute for an application contract.

// Navigation event only; do not treat this as data readiness.
await page.goto(url);
await page.waitForLoadState('domcontentloaded');
await expect(page.getByRole('main')).toBeVisible();

The Page API notes that most of the time waitForLoadState() is not needed before actions because Playwright auto-waits. Actions wait for their own actionability requirements; they cannot know which business result your screenshot must contain, so add a targeted assertion.

Rank #2
Philips 22 Inch Computer Monitor FHD 100Hz VA VESA Flicker-Free, 221V8LB
  • CRISP CLARITY: This 22 inch class (21.5″ viewable) Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
  • 100HZ FAST REFRESH RATE: 100Hz brings your favorite movies and video games to life. Stream, binge, and play effortlessly
  • SMOOTH ACTION WITH ADAPTIVE-SYNC: Adaptive-Sync technology ensures fluid action sequences and rapid response time. Every frame will be rendered smoothly with crystal clarity and without stutter
  • INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
  • THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors

Locator waits and meaningful conditions

Wait for visibility when visibility is the requirement

const panel = page.getByRole('region', { name: 'Results' });
await panel.waitFor({ state: 'visible' });
await panel.screenshot({ path: 'results-panel.png' });

A visible locator has a non-empty bounding box and is not visibility:hidden. It may still contain an empty shell. Prefer an assertion on text, a status, or a loaded child when those are what the screenshot needs.

Wait for text or status

await expect(page.getByRole('status')).toHaveText('Loaded');
await expect(page.getByTestId('profile-name')).toHaveText('Ada Lovelace');
await expect(page.locator('img[alt="Product photo"]')).toBeVisible();

Assertions retry until their timeout. Keep the timeout long enough for the documented environment, but avoid hiding a broken page with a very large blanket timeout.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Wait for a transition from loading to ready

await expect(page.getByTestId('spinner')).toBeHidden();
await expect(page.getByTestId('table')).toBeVisible();
await expect(page.getByTestId('table-row')).toHaveCount(10);

Waiting for a spinner to disappear is useful only when paired with a positive ready condition; a crash or empty response can also remove a spinner.

Stabilizing pixels before capture

Animations and transitions

Screenshot assertions disable animations by default. Finite animations are fast-forwarded to completion and transitionend is fired; infinite animations are canceled to their initial state and replayed after capture. Direct locator screenshots document allow as the default, so set the behavior explicitly when motion can alter pixels. These details are covered in the LocatorAssertions API and Playwright’s visual comparisons guide.

await expect(page.locator('.hero')).toHaveScreenshot('hero.png', {
  animations: 'disabled'
});

await page.locator('.hero').screenshot({
  path: 'hero-artifact.png',
  animations: 'disabled'
});

Hover, caret, and pointer state

Move the mouse away from hover-sensitive controls, or hover an element whose hover style does not change the image. A blinking text caret, focus ring, timestamp, random identifier, rotating carousel, or live counter can also create differences. Remove or freeze those sources in test mode, or mask the dynamic region where the assertion API supports it.

Rank #3
Sale
Dell 24 Monitor - SE2426H - 23.8-inch FHD (1920x1080) 144Hz 1ms Display, in-Plane Switching (IPS) Technology, AMD FreeSync™, TÜV 3-Star 2X HDMI, Tilt
  • Clear visuals. Fluid motion: A 144Hz refresh rate and 1ms MPRT deliver smooth, tear‑free motion across work, gaming, and streaming for clearer, more fluid viewing.
  • Eye comfort: TÜV Rheinland 3‑star* certification reduces harmful blue light while preserving stunning color quality without compromise. *TÜV Rheinland 3-star eye comfort certification.
  • Wide viewing angle: Get consistent views across a wide 178° /178° viewing angle.
  • In-Plane Switching (IPS): See excellent color accuracy and consistency across wide viewing angles with In-plane Switching (IPS) technology.
  • Ultra-thin bezels: Maximize your viewing experience with thin bezels.
await page.mouse.move(0, 0);
await page.getByRole('button', { name: 'Menu' }).focus();
// Assert the intended focus state, or blur it before a page-wide capture.
await page.mouse.click(0, 0);
await expect(page).toHaveScreenshot('stable-page.png', { fullPage: true });

Lazy images and fonts

Assert the image or content that must appear rather than assuming that a visible container means its descendants are ready. For a critical image, wait for its completed state:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const image = page.locator('img[alt="Product photo"]');
await expect(image).toBeVisible();
await expect(image).toHaveJSProperty('complete', true);
await expect(page).toHaveScreenshot('product.png');

If your application exposes a more meaningful “data loaded” status, prefer that over implementation details such as a particular image property.

Full-page and element capture decisions

  • Full page: use fullPage: true when the deliverable includes content below the viewport. Ensure lazy-loaded sections are triggered or otherwise asserted before capture.
  • Element: use a locator screenshot when surrounding layout is irrelevant. The locator is scrolled into view automatically, but asynchronous application work still needs its own assertions.
  • Page assertion: use toHaveScreenshot() when a baseline comparison is the goal.
  • Artifact: use page.screenshot() or locator.screenshot() when another system consumes the image.

Common failures and fixes

Blank or partially rendered screenshot

Cause: capture happened after navigation but before client rendering or data completion.

Fix: assert a heading, status, row count, or expected text that proves the required state. Do not replace that assertion with a fixed sleep.

Timeout waiting for networkidle

Cause: polling, analytics, streaming, or another persistent connection keeps the page active.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Samsung 27" Essential S3 (S36GD) Series FHD 1800R Curved Computer Monitor
  • CURVED FOR ENHANCED ENGAGEMENT: An immersive viewing experience with a curved monitor that wraps more closely around your field of vision; It creates a wider view, enhancing depth perception and minimizing peripheral distraction
  • SMOOTH PERFORMANCE FOR SEAMLESS CONTENT: Stay in the action when playing games, watching videos, or working on creative projects; The 100Hz refresh rate reduces lag and motion blur so you don't miss a thing in fast-paced moments¹
  • MORE GAMING POWER: Gain the edge with optimizable game settings; Color and image contrast can be adjusted to see scenes more vividly and spot enemies hiding in the dark; Game Mode adjusts any game to fill the screen so you can view every detail²
  • KEEP IT EASY ON THE EYES: Care for your eyes and stay comfortable, even during long sessions; Advanced eye comfort technology certified by TÜV reduces eye strain by minimizing blue light and reducing irritating screen flicker²
  • INCREASED VERSATILITY: Connect to more; Plug devices straight into your monitor for increased flexibility, making your computing environment even more convenient

Fix: remove the network-idle wait and assert the specific UI result. If you need a navigation milestone, use domcontentloaded or load only for that purpose.

Flaky visual diffs

Cause: animations, hover state, caret blinking, dynamic data, or an unstable font/image.

Fix: disable animations, move the pointer, freeze test data, wait for critical media, and use screenshot assertions rather than an immediate image comparison.

“Element is not attached” during locator capture

Cause: the framework replaced the node while Playwright was scrolling or capturing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix: wait for the application to finish replacing the component, then reacquire the locator and assert its ready state before calling screenshot().

Best Value
Sale
Sceptre New 22-Inch Gaming Monitor, FHD 1080p, Up to 144Hz, HDMI, DisplayPort, Built-in Speakers, Machine Black (E225W-FW144 Series, 2026)
  • 【INTEGRATED SPEAKERS】Whether you're at work or in the midst of an intense gaming session, our built-in speakers provide rich and seamless audio, all while keeping your desk clutter-free.
  • 【EASY ON THE EYES】 Protect your eyes and enhance your comfort with Blue-Light Shift technology. This feature reduces harmful blue light emissions from your screen, helping to alleviate eye strain during long hours of use and promoting healthier viewing habits.
  • 【WIDEN YOUR PERSPECTIVE】Our sleek minimal bezel design ensures undivided attention. The nearly bezel-free display seamlessly connects in a dual monitor arrangement, delivering an unobstructed view that lets you focus on more at once, completely distraction-free.

Deprecated selector waiting

Cause: use of page.waitForSelector(), which the current API marks as discouraged in favor of locator-based waits and web assertions.

Fix: migrate to locator.waitFor(), expect(locator).toBeVisible(), or a more specific assertion.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and reliability guidance

  • Use the narrowest assertion that proves readiness; a page-wide wait can make failures slower and less diagnostic.
  • Prefer stable test data and deterministic clocks where timestamps or rotating content are visible.
  • Capture the smallest target that answers the test’s question; full-page images cost more time and produce more unrelated diffs.
  • Set an explicit screenshot timeout appropriate to your CI environment, while retaining a shorter timeout for obviously missing elements.
  • Keep navigation, readiness assertions, stabilization, and capture as separate steps so a failure identifies the missing phase.
  • Check the Playwright version installed in your project against the current API documentation. The API pages identify locator.screenshot as introduced in v1.14, locator.waitFor in v1.16, and screenshot assertions in v1.23; these are method-introduction notes, not a minimum-version recommendation.

Or skip the browser setup: ScreenshotNeo

If you need a screenshot from a URL rather than a browser test you maintain, ScreenshotNeo provides a single GET request that returns PNG, JPEG, WebP, or PDF. Before capture it accepts cookie/consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also offers an MCP server for Claude, Cursor, and other MCP clients, with take_screenshot, get_page_info, and capture_pdf tools.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For the complete parameter list, see the ScreenshotNeo documentation.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

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)

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 includes full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper and page-range controls, custom CSS and JavaScript, click-before-capture, selector hiding, waits for a selector, delay, or network idle, request/resource blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed public image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, an OpenAPI specification, and compatibility with parameter names used by other screenshot APIs.

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 to start.

Frequently Asked Questions

How long should I wait before a Playwright screenshot?

There is no universal delay. Wait for the specific text, status, locator, or application state that must appear in the image, then capture it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does a visible locator mean the screenshot is ready?

Only if visibility is the complete prerequisite. Visibility does not guarantee that nested data, images, fonts, or animations have finished.

Can I use screenshot assertions outside Playwright Test?

No. toHaveScreenshot() is part of the Playwright Test assertion APIs; library-only scripts should use direct screenshots plus explicit readiness checks.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.