Playwright Interaction Testing: Capture UI States for Review
Capture stable UI states with Playwright, review visual changes against baselines, and diagnose failures with assertions and traces.
Use Playwright Test’s expect(page).toHaveScreenshot() after the interaction that creates the UI state you want to review. On its first run, Playwright creates a reference image; on later runs, it compares the rendered page with that baseline. Pair the visual check with focused assertions for behavior, keep baseline and comparison environments consistent, and inspect diffs instead of accepting them automatically.
This workflow is for Playwright Test. The screenshot assertion is provided by its test runner; it is not a standalone screenshot-comparison method for every Playwright setup. See Playwright’s visual comparisons guide and PageAssertions API.
1. Install and configure Playwright Test
If your project already has Playwright Test, use its installed version and existing configuration. Otherwise, install the test runner:
npm init playwright@latest
The setup command creates a configuration and example tests. The following example uses TypeScript, which Playwright’s Node.js test runner supports directly. Save it as tests/checkout-visual.spec.ts. The example assumes the application is available at http://127.0.0.1:3000, and that it has a button named “Open checkout” which opens a dialog containing a “Continue” button.
import { test, expect } from '@playwright/test';
test('checkout dialog has the expected UI', async ({ page }) => {
await page.goto('http://127.0.0.1:3000');
await page.getByRole('button', { name: 'Open checkout' }).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible();
await expect(dialog).toContainText('Choose a payment method');
// Capture the dialog itself, rather than unrelated page content.
await expect(dialog).toHaveScreenshot('checkout-dialog.png');
});
Run the test with npx playwright test tests/checkout-visual.spec.ts. On the first run, inspect the generated expected image under the project’s snapshot output and commit it only after confirming it represents the intended state. The test output identifies the expected-image location if you need to find it. Playwright names screenshot baselines with project and platform information as needed.
2. Drive the page to a meaningful state
A visual test should capture a state that matters to a user: an open menu, validation error, selected filter, expanded row, completed submission, or responsive layout. First use locators and actions to reach that state. Then assert important outcomes semantically, such as the URL, visible text, or dialog visibility. These assertions make failures precise and complement the image comparison.
import { test, expect } from '@playwright/test';
test('filter selection updates the results view', async ({ page }) => {
await page.goto('http://127.0.0.1:3000/catalog');
await page.getByRole('button', { name: 'Availability' }).click();
await page.getByRole('checkbox', { name: 'In stock' }).check();
await expect(page).toHaveURL(/availability=in-stock/);
await expect(page.getByRole('heading', { name: 'In stock items' })).toBeVisible();
await expect(page).toHaveScreenshot('catalog-in-stock.png', {
fullPage: true,
});
});
Use a page screenshot when the overall composition is part of the contract. Use a locator screenshot when only one component matters. Use fullPage: true when content below the viewport is relevant; otherwise, viewport capture is faster to review and limits the comparison scope. The API reference documents the page assertion options, and locator assertions are also available for focused captures.
3. Review and update baselines deliberately
The first successful capture establishes the expected image. Treat it as reviewable test data: inspect the image, confirm the interaction reached the right state, and commit it with the test. When a later run fails, compare the actual image, expected image, and diff produced by the test runner. Decide whether the change is an intended UI update or a regression before changing the baseline.
When a visual change is intentional, update snapshots through Playwright Test’s update workflow, for example npx playwright test --update-snapshots, then review the resulting image changes in the same code review as the UI change. Do not use snapshot updating as a generic response to a failure: that replaces the reference and can hide an unexplained regression. The Playwright visual comparisons guide describes the baseline workflow.
4. Control incidental visual differences
Dynamic content can make a useful test noisy. First identify what varies and why; then choose a control that preserves the part of the UI you intend to verify. Playwright waits for two consecutive screenshots to match before comparing, which helps avoid capturing a transient frame, but it cannot make genuinely changing content deterministic.
| Source of noise | Useful control | Trade-off |
|---|---|---|
| Animation or transition | Use animations: 'disabled', which is the screenshot assertion default. |
Finite animations are fast-forwarded; infinite animations are canceled for capture and resumed afterward. The image represents a stable state, not the motion itself. |
| Timestamp, rotating promotion, or volatile region | Mask the changing locator with mask, or hide/normalize it through a screenshot stylesheet. |
The masked region is excluded from meaningful visual review. Keep the mask narrow and documented. |
| Environment-specific visual rendering | Run baselines and comparisons in the same browser and operating-system environment, or maintain project-specific baselines. | Separate environments require separate references and review. |
| Subpixel or antialiasing differences | Consider a small, justified threshold, maxDiffPixels, or maxDiffPixelRatio. |
Tolerance can allow real changes through. It does not prove a difference is harmless. |
| Long page with irrelevant regions | Capture a locator or use a defined clip instead of the entire page. | Anything outside the selected region is no longer covered by that visual assertion. |
Example with a mask and a screenshot stylesheet:
await expect(page).toHaveScreenshot('dashboard.png', {
fullPage: true,
mask: [page.getByTestId('last-updated')],
stylePath: 'tests/visual-stability.css',
});
/* tests/visual-stability.css */
/* Hide a volatile timestamp while preserving the surrounding layout. */
[data-testid="live-clock"] {
visibility: hidden !important;
}
The screenshot stylesheet is documented as applying through Shadow DOM and inner frames. The stylePath option was added in Playwright v1.41; check the API reference for the version installed in your project. Use only options supported by that version. More screenshot controls and their exact behavior are in the PageAssertions reference.
5. Keep visual, semantic, and accessibility checks distinct
A screenshot tells you what pixels were rendered. It does not reliably explain why they changed or prove that controls have the correct semantics. Add focused assertions for important behavior: a dialog is visible, an error message is present, the URL changed, or a form value is correct. Playwright’s assertions guide covers retrying assertions such as toBeVisible() and toHaveText().
ARIA snapshots describe accessible structure and can complement visual snapshots, but they answer a different question: whether the accessible representation has the expected structure. They do not replace visual review. See Playwright’s ARIA snapshots guide.
6. Diagnose a failure with the diff and trace
- Read the assertion failure and locate the expected image, actual image, and diff output.
- Check whether the semantic assertions passed. If they failed, fix the interaction or application behavior before interpreting the visual diff.
- Inspect the diff in context. Look for layout shifts, missing content, changed fonts, animation frames, and environment differences.
- Open the test trace when the sequence needs investigation. Use it to review actions, DOM snapshots, and execution details around the failure.
- Rerun in the same configured browser and environment to determine whether the difference is reproducible.
- Update the baseline only after confirming that the new rendering is intended.
Playwright’s Trace Viewer guide explains how to inspect actions and DOM snapshots around a failure. A trace gives interaction and DOM context; the screenshot diff shows the visual change. Together with semantic assertions, these help identify whether the failure is behavioral, visual, or caused by the test environment.
7. cURL, Python, and Node.js for capturing a review artifact
Playwright Test is the appropriate path when you need an assertion against a committed screenshot baseline. If you instead need a screenshot file from a URL outside that test-runner workflow, these examples capture one with ScreenshotNeo. The API returns an image or PDF from one GET request. See the ScreenshotNeo API documentation for parameters and response details.
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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) =>
writeFile('shot.webp', Buffer.from(await res.arrayBuffer()))
);
8. Or skip the browser setup
For a URL-based capture without maintaining browser setup, use ScreenshotNeo’s website screenshot API. This one-call example returns a screenshot file:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Every feature is available on every plan. This URL-based capture is useful for review artifacts, while Playwright Test remains the workflow for exercising interactions and comparing checked-in visual baselines.
Create a free ScreenshotNeo account and capture 1,000 screenshots a month with no card.
9. Performance, reliability, and cost
- Keep the comparison scope focused. A locator capture or viewport image compares less content than a full-page image. Use full-page mode only when below-the-fold rendering is part of the state under review.
- Stabilize the environment. Browser rendering varies with operating system, browser version, settings, hardware, power source, and headless mode. Keep the baseline and comparison configuration consistent. This caveat is documented in Playwright visual comparisons.
- Make the test repeatable. Use stable locators and deterministic data where possible. Mask or normalize only genuinely volatile content, and keep tolerance values narrow and reasoned.
- Review failure artifacts. Baselines and diffs add files to review. Keep screenshots named for the state they represent, and avoid a single broad screenshot that makes every unrelated page change fail the same assertion.
- Budget for maintenance. Visual tests need deliberate baseline review as the UI evolves. No source reviewed provides a universal runtime or cost benchmark; measure your own suite under its actual browsers and CI environment.
- For URL captures, know the billing behavior. ScreenshotNeo states that only clean shots are billed; its response includes
X-Page-VerdictandX-Billedheaders. Free usage is 1,000 shots per month without a card, with paid plans beginning at $5 for 3,000.
10. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
toHaveScreenshot is undefined or unavailable |
The test is not running with Playwright Test, or the assertion import/setup is wrong. | Use expect from @playwright/test and run the test with the Playwright Test runner. Check the installed version’s API reference. |
| The first run creates an unexpected baseline | The test reached the wrong state, or the reference was accepted without inspection. | Verify locators and semantic assertions, inspect the image, and only commit a baseline that matches the intended UI. |
| The screenshot changes on every run | Volatile content, animation, asynchronous loading, or unstable test data. | Wait for the relevant state, use deterministic fixtures, disable animation, and narrowly mask or normalize volatile regions. |
| Snapshots pass locally but fail in CI | Different OS, browser version, headless mode, fonts, hardware, or rendering settings. | Generate and compare baselines in a consistent environment, or keep separate baselines for distinct configured projects. |
| A diff is tiny but recurring | Rendering variation such as antialiasing or subpixel differences. | Confirm the environment first. If the remaining difference is acceptable, use a small documented threshold or pixel tolerance; do not increase it just to silence an unexplained change. |
| Full-page image misses or shifts lazy content | The page has not loaded the content needed for capture, or layout changes as content appears. | Wait for the relevant content or selector before capture, and assert that the content is visible. Limit the capture to the region that represents the test contract if appropriate. |
| The diff shows a changed page but not the cause | A screenshot reports pixels, not the action sequence or DOM context. | Inspect the trace and semantic assertion results alongside expected, actual, and diff images. |
stylePath is rejected |
The installed Playwright release predates the option. | Check the version-specific API reference; stylePath was added in v1.41. Upgrade only if the project can adopt that release. |
11. Frequently asked questions
How do I take a screenshot in Playwright?
For a file during a test, use the page or locator screenshot method. For a screenshot that is compared to a reference, use Playwright Test’s toHaveScreenshot() assertion.
How do I compare screenshots in Playwright?
Call await expect(page).toHaveScreenshot('name.png') in a Playwright Test test. Review the generated first-run reference and subsequent diffs.
Can screenshot assertions run outside Playwright Test?
The screenshot assertion API is for Playwright Test. A standalone script can capture an image, but the documented assertion and baseline workflow require the test runner.
Should I use toMatchSnapshot() for screenshots?
Use toHaveScreenshot() for image comparison. Playwright’s SnapshotAssertions API cautions against using toMatchSnapshot() for screenshots.
Do ARIA snapshots replace screenshot tests?
No. ARIA snapshots capture accessible structure; screenshot assertions compare visual rendering. They can cover complementary expectations.


