How to Compare Playwright Screenshots with a Custom Pixel Threshold
Use Playwright Test’s screenshot matcher to tune per-pixel sensitivity and total diff limits, reduce visual noise, and troubleshoot unreliable comparisons.
Use Playwright Test’s toHaveScreenshot() assertion. Set threshold to control the color difference that an individual pixel may have before Playwright counts it as different. Set maxDiffPixels or maxDiffPixelRatio to limit the total number or proportion of pixels allowed to differ.
import { test, expect } from '@playwright/test';
test('homepage visual baseline', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot({
threshold: 0.1,
maxDiffPixels: 100,
});
});
The values above are an example starting point, not a universal tolerance. Choose them based on the visual changes your project considers acceptable, and inspect the diff when you change them.
1. Understand the three comparison limits
| Option | What it controls | Documented default | Use it when |
|---|---|---|---|
threshold |
Per-pixel perceived color sensitivity. Playwright’s comparator uses the YIQ color space; the range is 0 (strict) to 1 (lax). | 0.2 |
Small color or rendering variations should or should not count as a changed pixel. |
maxDiffPixels |
Maximum number of pixels allowed to be classified as different. | Unset | You want a fixed pixel budget regardless of image dimensions. |
maxDiffPixelRatio |
Maximum fraction of the image’s pixels allowed to differ, from 0 to 1. | Unset | You want the allowed share of differences to scale with image dimensions. |
These settings answer different questions. threshold decides whether a particular pixel differs enough to count. The count and ratio options decide how many counted differences the assertion can tolerate. Raising both can hide a subtle local change and allow a broad regression to pass.
Use either an absolute allowance or a ratio according to your requirement. For example, if an allowance of at most 100 pixels is meaningful at every viewport, use maxDiffPixels. If the acceptable share should remain similar for screenshots of different sizes, use maxDiffPixelRatio. The API documents the controls, but does not prescribe a correct value for every application.
2. Add a screenshot comparison to a Playwright test
Install Playwright Test in the project and use the test runner; screenshot assertions are provided by Playwright Test. A page assertion is useful for a whole page, while a locator assertion targets a particular element.
import { test, expect } from '@playwright/test';
test('homepage matches its visual baseline', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png', {
threshold: 0.1,
maxDiffPixels: 100,
});
});
test('navigation matches its visual baseline', async ({ page }) => {
await page.goto('/');
const navigation = page.locator('nav');
await expect(navigation).toHaveScreenshot('navigation.png', {
threshold: 0.1,
maxDiffPixelRatio: 0.005,
});
});
On the first run, the test runner creates the expected screenshot baseline. Review and commit that baseline through your normal code review process. On subsequent runs, Playwright compares the capture with the expected image. The assertion waits for two consecutive page screenshots to produce the same result, then compares the last one with the expectation.
Use toHaveScreenshot() for screenshot comparisons. Playwright’s snapshot API reference specifically recommends this matcher for comparing screenshots rather than using toMatchSnapshot() for that purpose.
3. Set project-wide defaults
Place common screenshot tolerances under expect.toHaveScreenshot in playwright.config.ts. Individual assertions can still specify different values where a test needs a separate policy.
import { defineConfig } from '@playwright/test';
export default defineConfig({
expect: {
toHaveScreenshot: {
threshold: 0.1,
maxDiffPixels: 100,
},
},
});
Keep project defaults conservative enough to reveal changes that matter to the team. A per-test override is appropriate for a known special case, but document why it differs so an unusually broad allowance does not become invisible policy.
4. Make captures repeatable before relaxing tolerances
A pixel threshold cannot make an unstable capture reliable. First reduce differences caused by varying input or page state, then decide which remaining visual differences should count.
- Keep the capture environment consistent. Use the same browser and viewport conditions for the baseline and comparison. Differences in the capture environment can alter rendered pixels.
- Control volatile content. Use screenshot styling or masking for genuinely irrelevant dynamic regions such as timestamps or rotating content. Playwright’s visual comparison guide describes applying a stylesheet during capture to filter dynamic elements.
- Control interaction state. Hover effects are included when the element is hovered at capture time. Ensure the pointer and page state are intentional before the assertion.
- Inspect the comparison. Confirm whether a difference is harmless rendering noise or an actual UI change before increasing tolerance.
- Adjust one axis at a time. Change per-pixel sensitivity separately from the total pixel count or ratio, then review the resulting diff.
A passing assertion means the image stayed within the configured limits. It does not prove that every visual change is harmless. The expected screenshot and generated diff remain part of the review.
5. Choose a tolerance without hiding regressions
- Start with Playwright’s documented
thresholddefault of0.2, or set a stricter value if even modest per-pixel changes should be detected. - Leave the aggregate allowance unset when any detected difference should fail, or choose a small count or ratio based on a deliberate acceptance rule.
- Do not raise
thresholdto compensate for an unstable page. Stabilize or mask the volatile part instead. - Do not grant a large
maxDiffPixelsallowance simply because a large screenshot naturally contains more pixels. Consider a ratio if the acceptable fraction should scale with image size. - After changing thresholds, inspect representative diffs, including a real intended change and a change that should fail.
For a rough conversion between the two aggregate controls, multiply the screenshot’s total pixel count by the desired ratio to estimate a pixel allowance. This is only a sizing aid: screenshots can have different dimensions, and the team must still decide what differences are meaningful.
6. Troubleshooting common comparison failures
| Symptom | Likely cause | Fix |
|---|---|---|
| The screenshot assertion fails on every run with small differences. | Volatile content, unintended hover state, or inconsistent capture conditions. | Make the page state and environment repeatable; use screenshot styling or masking for genuinely irrelevant regions before changing tolerance. |
| A meaningful UI change passes unexpectedly. | threshold is too lax, or the aggregate pixel count or ratio is too large. |
Inspect the diff and tighten the relevant axis. Lower threshold for per-pixel sensitivity, or reduce the aggregate allowance for the total number of changed pixels. |
| Minor color variation creates noisy failures. | threshold is strict for the project’s rendering variation. |
Confirm the variation is acceptable, then increase threshold modestly and inspect the diff. Do not use this to excuse structural changes. |
| An element assertion captures the wrong region or fails to locate it. | The locator does not identify the intended element or is not ready when the assertion runs. | Use a locator that uniquely identifies the target and ensure the page has reached the state required by the test before asserting. |
| The baseline is missing or unexpected after an update. | The screenshot was first generated, or the application’s intended appearance changed. | Review the actual image and diff, then update the expected baseline only when the visual change is intended. |
| The screenshot differs because an animation or transient state was captured. | The capture occurred during a changing visual state. | Make the captured state deterministic and use screenshot-time styling or masking for irrelevant motion where appropriate. |
7. Performance, reliability, and cost
Playwright’s screenshot matcher captures the page and waits for consecutive screenshots to stabilize before it compares against the baseline. Dynamic pages can therefore consume more test time or remain difficult to compare until their state is controlled. Scope assertions to the page or element that answers the test’s question, and avoid repeatedly capturing large regions when a smaller element assertion is sufficient.
Thresholds are a reliability tradeoff: strict comparisons reveal small changes but can be sensitive to capture variation; permissive comparisons reduce some noise but can conceal regressions. A stable capture setup and reviewed baselines are the main controls for trustworthy results.
The Playwright comparison itself is a local test workflow; the cited Playwright documentation does not state a per-screenshot service charge. Budget for the compute and CI time your browser tests use, and keep retries or repeated captures from obscuring persistent visual failures.
8. Or skip the browser setup
If you need screenshot files or PDFs from URLs without setting up browser capture infrastructure, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. A single GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for request options.
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}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
- Cookie and consent banners are accepted like a visitor, and 60+ known consent platforms, newsletter popups, and chat widgets are removed before capture. Each step can be turned off.
- Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers report the page verdict and billing status.
- An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
- 1,000 screenshots per month are free with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo’s free plan and get 1,000 screenshots per month with no card.
9. Frequently asked questions
What is the default Playwright screenshot threshold?
The documented default for threshold is 0.2. The default maximum difference count and ratio are unset.
Can I use both maxDiffPixels and maxDiffPixelRatio?
They express alternative aggregate limits: an absolute count and a proportion. Pick the limit that matches your acceptance rule and consult the current matcher API if you need to combine options.
Does threshold: 0 mean no pixels may differ?
It makes per-pixel color sensitivity strict. The aggregate allowance is a separate setting, so configure that too if any detected difference must fail.
Does ScreenshotNeo compare against Playwright baselines?
The ScreenshotNeo facts for this article describe URL capture to image or PDF; they do not include Playwright baseline comparison. Use Playwright Test’s screenshot matcher for the baseline assertion.


