How to Compare Screenshots in Playwright
Use Playwright Test’s screenshot assertions to compare pages or components against reviewed baselines. Learn how to tune tolerances, control visual noise, and fix common failures.
Use Playwright Test’s toHaveScreenshot() assertion to compare a page with a saved reference image. For a component, use the corresponding locator assertion. The first run creates a baseline; later runs compare new captures against it. Review and commit baselines as test data, and update them only after confirming that a visual change is intentional.
Playwright’s visual comparison guide notes that browser rendering can vary with the host operating system, browser version, settings, hardware, power source, headless mode, and other factors. Keep baseline creation and comparison in a stable, consistent environment.
1. Add a page screenshot assertion
This example uses Playwright Test. Save it as tests/homepage.spec.ts in a project configured with @playwright/test:
import { test, expect } from '@playwright/test';
test('homepage matches its visual baseline', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png');
});
The first run creates the expected screenshot. Inspect the generated image before committing it. Run the test again to compare a fresh capture to that reference. Playwright’s snapshot file names include browser and platform information by default, or the project name when configured, so different rendering environments can have separate expected images.
2. Compare a component instead of the whole page
Use a locator screenshot assertion when the feature under test is a component. A locator narrows the captured area and avoids making unrelated page regions part of that component’s visual contract.
import { test, expect } from '@playwright/test';
test('pricing card matches its visual baseline', async ({ page }) => {
await page.goto('/pricing');
const card = page.getByTestId('pricing-card');
await expect(card).toHaveScreenshot('pricing-card.png');
});
Choose a locator that identifies the intended element reliably. If the element is absent, hidden, or ambiguous, fix the page state or locator before changing screenshot tolerances.
3. Create, review, and update baselines
- Run the visual test to generate its initial reference image.
- Open the image and confirm that it shows the intended page state, viewport, and content.
- Commit the reference image with the test so other developers and CI compare against the same reviewed data.
- When a deliberate design change alters the result, run
npx playwright test --update-snapshots. - Inspect the updated images, then commit the approved baseline changes.
Do not update snapshots merely because a test failed. A failing comparison may reveal a regression, a changed environment, or uncontrolled page state. Review the actual and expected images first.
4. Choose comparison tolerances
Playwright exposes settings for pixel-level color sensitivity and for the total amount of permitted difference. They control different things:
| Option | What it controls | How to use it |
|---|---|---|
threshold |
Per-pixel perceived color difference. Playwright documents a default of 0.2. |
Lower values are stricter; higher values permit more color variation. |
maxDiffPixels |
Maximum absolute number of pixels allowed to differ. | Useful when the screenshot dimensions are fixed. The docs show 100 as an example, not a universal recommendation. |
maxDiffPixelRatio |
Maximum fraction of the image allowed to differ. | Useful when image dimensions vary and a proportional cap is more appropriate. |
For example, set an explicit limit on a single assertion when a small amount of rendering variation is acceptable:
import { test, expect } from '@playwright/test';
test('dashboard remains visually stable', async ({ page }) => {
await page.goto('/dashboard');
await expect(page).toHaveScreenshot('dashboard.png', {
threshold: 0.2,
maxDiffPixels: 100,
});
});
The numbers above illustrate configuration syntax; choose limits based on the intended sensitivity of your test. Avoid increasing tolerances simply to hide noisy failures. First find out whether fonts, data, animations, hover state, viewport, browser version, or host environment changed.
When a consistent policy fits the suite, configure screenshot assertion defaults globally or per project through Playwright’s expect.toHaveScreenshot configuration. Check the documentation for the Playwright version installed in your project before relying on option defaults or configuration details.
5. Reduce visual noise before capturing
Stable inputs make comparisons more useful. Establish the state that matters, make test data deterministic, and ensure fonts and assets are available before the assertion. Keep the viewport, browser, and execution environment consistent between baseline generation and comparison.
Hide known dynamic regions with a style path
Playwright supports stylePath to inject CSS that filters dynamic elements during screenshot capture. For example, create a stylesheet that hides a changing timestamp or live ticker:
/* tests/visual-stability.css */
[data-visual-volatile] {
visibility: hidden !important;
}
import { test, expect } from '@playwright/test';
test('account page matches its baseline', async ({ page }) => {
await page.goto('/account');
await expect(page).toHaveScreenshot('account.png', {
stylePath: 'tests/visual-stability.css',
});
});
Mark only content that is genuinely irrelevant to the visual contract. Hiding too much can allow meaningful regressions to pass unnoticed.
Control pointer and hover state
Playwright captures hover effects that are present. Move the pointer away from the target before capture if the default state is intended, or deliberately establish the expected hover state and keep it consistent.
import { test, expect } from '@playwright/test';
test('navigation default state matches baseline', async ({ page }) => {
await page.goto('/');
await page.mouse.move(0, 0);
await expect(page).toHaveScreenshot('homepage.png');
});
For a hover-specific visual test, move the pointer over the intended element before taking the screenshot, and use a baseline named for that state.
6. Keep baselines reproducible across machines and CI
Visual output can differ across operating systems, browser versions, settings, hardware, power state, and headless mode. Use a consistent CI image and browser setup for both baseline generation and comparison where possible. If your projects intentionally target different browsers or platforms, retain the separate expected baselines those environments need instead of treating their rendering as interchangeable.
When a local run fails but CI passes, or the reverse, compare the browser and platform project, viewport, fonts, device scale, and headless configuration before updating any reference image. The failure may be environmental rather than a product change.
7. Understand common failures
| Symptom | Likely cause | What to do |
|---|---|---|
| First run reports a missing snapshot | No baseline exists yet. | Generate it with the test, inspect the image, and commit the approved reference. |
| Comparison fails after a UI change | The rendered page differs from the reviewed baseline. | Inspect expected and actual images. Fix an unintended regression, or update and review the baseline for an intentional change. |
| Many small differences appear across machines | Browser, operating system, fonts, hardware, headless mode, or other environment details differ. | Align the baseline and comparison environment; use appropriate project-specific baselines where environments are intentionally distinct. |
| Only an animated or live region changes | Content is volatile during capture. | Stabilize the test state or use stylePath to filter a known irrelevant region. |
| An unexpected element appears highlighted | The pointer is over it and a hover style is active. | Move the pointer away for the default state, or establish the intended hover state before capture. |
| Raising tolerance makes unrelated changes pass | The tolerance is masking differences instead of addressing their source. | Restore a meaningful threshold and investigate data, assets, fonts, layout, viewport, and environment. |
| Screenshot assertion APIs are unavailable | The project is not using the Playwright Test runner. | Use Playwright Test for toHaveScreenshot(); screenshot assertions require its test runner. |
8. Use the screenshot-specific assertion
toHaveScreenshot() is Playwright Test’s screenshot comparison assertion. Use it for page or locator screenshots. toMatchSnapshot() is intended for strings or buffers and is not the preferred screenshot comparison API; Playwright’s snapshot assertion documentation directs screenshot comparisons to toHaveScreenshot(). These assertion APIs require the Playwright Test runner.
Named screenshot baselines use PNG by default. Playwright also documents WebP snapshots as lossless when the filename ends in .webp.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. A single GET request returns a PNG, JPEG, WebP, or PDF. For the available options, see the ScreenshotNeo API documentation.
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 like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, failed loads, timeouts, and cache hits are not billed, and responses identify the page verdict and billing status in headers. An MCP server gives AI agents tools to take screenshots, inspect page information, and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
Performance, reliability, and cost
Playwright screenshot comparison runs as part of your test suite, so its cost and runtime are tied to the browser work your tests perform. Keep captures focused: a locator assertion can avoid capturing unrelated page content when the component is what you need to verify. A full page assertion is useful when layout across the page is part of the contract.
Reliability depends on a stable capture state and a consistent rendering environment. Baselines are files that need review and version control; they are not self-validating truth. Keep CI’s browser and operating system setup predictable, and update references only after inspecting the changes.
For external captures, ScreenshotNeo bills only clean shots; bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Its listed monthly plans are Free with 1,000 shots and no card, Starter at $5 for 3,000, Growth at $15 for 15,000, Pro at $39 for 60,000, Scale at $99 for 250,000, and Business at $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. These are service capture prices, not a replacement for committing and reviewing test baselines.
Frequently asked questions
Can I compare screenshots without Playwright Test?
Playwright’s toHaveScreenshot() assertion is part of Playwright Test and requires its runner. Use that runner when you want Playwright-managed screenshot baselines and assertions.
Should I use a pixel count or a ratio limit?
Use maxDiffPixels when an absolute pixel allowance suits fixed-size captures. Use maxDiffPixelRatio when a proportional allowance better fits screenshots whose dimensions differ. Neither setting replaces a suitable per-pixel threshold.
Can the baseline be WebP?
Yes. Playwright documents WebP as lossless; use a named screenshot ending in .webp to choose that format.
When should I update a screenshot baseline?
After confirming the rendered change is intentional, reviewing the new image, and deciding it is the expected design. Then update snapshots and commit the reviewed reference.


