ScreenshotNeo

BlogHow-to

How to Compare Playwright Screenshot Snapshots with a Tolerance

Learn how Playwright’s screenshot threshold differs from pixel-count limits, configure a sensible tolerance, and keep visual tests stable and useful.

By the ScreenshotNeo team4 October 20267 min read

Use Playwright Test’s expect(page).toHaveScreenshot() assertion to compare a page with its stored screenshot. Set threshold to control how much perceived color difference makes an individual pixel count as changed; use maxDiffPixels or maxDiffPixelRatio to cap how many changed pixels the assertion accepts. These options control different things.

For example, this allows at most 100 differing pixels while retaining the default per-pixel threshold:

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

test('landing page matches its visual baseline', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveScreenshot({
    maxDiffPixels: 100,
  });
});

The value is an example, not a universal recommendation. Choose a narrow allowance that fits your page and review the generated diff. Playwright documents the default pixel comparison threshold as 0.2; maximum-difference limits are unset unless you configure them. Playwright’s visual comparisons guide and TestConfig API explain the behavior and options.

1. Understand the three tolerance options

Option What it controls Default and range Use it when
threshold Per-pixel perceived color difference. A pixel above the threshold is counted as different. Pixelmatch default is 0.2; documented range is 0 (strict) to 1 (lax). Small color-rendering differences should or should not count as mismatches.
maxDiffPixels Maximum absolute count of pixels allowed to differ. Unset by default. A fixed pixel allowance is understandable for screenshots with known dimensions.
maxDiffPixelRatio Maximum share of pixels allowed to differ. Unset by default; range 0 to 1. You compare images with different dimensions and want the cap to scale with image area.

Think of the comparison in two stages: the threshold determines which pixels count as different, then a maximum-difference option determines whether the total mismatch is acceptable. Raising threshold does not mean “allow more changed pixels”; it makes the comparison less sensitive to color differences at each pixel.

2. Configure tolerance for a test

Choose one maximum-difference limit so the policy is easy to understand. An absolute count gives a fixed budget; a ratio scales with screenshot size. You can also set a deliberately chosen per-pixel threshold.

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

test('account page tolerates a small known rendering difference', async ({ page }) => {
  await page.goto('/account');
  await expect(page).toHaveScreenshot({
    threshold: 0.2,
    maxDiffPixels: 80,
  });
});

test('responsive page uses a proportional mismatch allowance', async ({ page }) => {
  await page.setViewportSize({ width: 1280, height: 800 });
  await page.goto('/');
  await expect(page).toHaveScreenshot({
    maxDiffPixelRatio: 0.001,
  });
});

The values above show syntax only. Playwright does not publish one best allowance for every application. Start from the default threshold, inspect what fails, and permit only the small, understood variation your test needs.

3. Set project-wide defaults

Use expect.toHaveScreenshot in playwright.config.ts when most visual tests should share the same policy. An individual assertion can still provide its own options where a page needs a specific allowance.

import { defineConfig } from '@playwright/test';

export default defineConfig({
  expect: {
    toHaveScreenshot: {
      threshold: 0.2,
      maxDiffPixels: 100,
    },
  },
});

Do not set both maximum-difference limits casually. A count and a ratio describe different budgets; using both can make the effective policy harder for the team to reason about. The API documents each option, but your project should decide which unit best matches its test images.

4. Make captures repeatable before relaxing tolerance

toHaveScreenshot() waits until two consecutive page screenshots are identical before comparing the capture with its baseline. This helps with transient capture instability, but it does not make different rendering environments identical. Browser rendering may vary with the operating system, browser version, settings, hardware, power source, and headless mode. Keep baseline generation and comparison in a consistent environment where possible. If platform differences are intentional, maintain platform-specific baselines.

Control capture-time variation before increasing the tolerance:

  • Animations: animations: 'disabled' is the default. Finite animations are fast-forwarded to completion; infinite animations are canceled to their initial state for capture, then resumed.
  • Caret: caret: 'hide' is the default and prevents the text caret from creating a difference.
  • Scale: scale: 'css' is the default, producing one image pixel per CSS pixel. scale: 'device' captures device pixels and can produce larger high-DPI images. Keep it consistent between baseline and comparison.
  • Dynamic content: Mask only elements whose changing appearance is irrelevant to the test. Masked areas are not being visually verified.
  • Capture stylesheet: stylePath applies a stylesheet during capture, which can hide volatile content. The API marks this option as added in Playwright v1.41; check your installed version.
import { test, expect } from '@playwright/test';

test('page screenshot ignores a clock and disables motion', async ({ page }) => {
  await page.goto('/dashboard');
  await expect(page).toHaveScreenshot({
    animations: 'disabled',
    caret: 'hide',
    scale: 'css',
    mask: [page.locator('[data-testid="live-clock"]')],
  });
});

Use masking or a capture stylesheet only for genuinely irrelevant fluctuations. A broad threshold or large pixel cap can hide meaningful layout, typography, color, or content changes.

5. Create, inspect, and update baselines

  1. Run the visual test without a baseline. Playwright Test creates a reference screenshot when one does not exist.
  2. Commit the snapshot directory with the test code so reviewers can inspect baseline changes.
  3. On later runs, inspect the expected image, actual image, and diff when a comparison fails. Determine whether the change is a product regression, an intended UI update, or capture noise.
  4. For an intentional visual change, review the new screenshot and then update snapshots with npx playwright test --update-snapshots.

Screenshot assertion snapshots are PNG by default; the API also documents .webp snapshot names. Both formats are lossless. For screenshot comparison, use expect(page).toHaveScreenshot() rather than calling toMatchSnapshot() directly. See the SnapshotAssertions API.

6. Choose between pixel count and pixel ratio

  • Choose maxDiffPixels when your screenshot dimensions are fixed and a count is easy to interpret.
  • Choose maxDiffPixelRatio when image dimensions vary and a proportional budget is more useful.
  • Use threshold to tune color sensitivity, not to compensate for a large region moving or changing.
  • Keep the cap low enough that an unintended UI change still causes a failure. There is no documented universal best value.

7. Troubleshoot common failures

Symptom Likely cause What to do
A test fails with a very small scattered diff. Rendering variation, animation, caret, or volatile content. Compare in the same OS/browser/headless setup; disable animation, hide the caret, or mask only irrelevant dynamic elements.
Large portions of the page differ. A real layout/content change, different viewport, or different device scale. Check the actual and expected images, viewport, browser, and screenshot scale. Fix the page or intentionally review and update the baseline.
Changing threshold does not resolve a large diff. The mismatch is likely broad or structural; threshold only changes per-pixel color sensitivity. Inspect the diff and use an appropriate max-difference cap only if the changed pixels are understood and acceptable.
The test fails on CI but passes locally. Different OS, browser build, settings, hardware, or headless mode. Make baseline creation and comparison use the same environment, or maintain separate baselines for intended platform differences.
The assertion option is rejected or ignored. The installed Playwright version may not support that option, or configuration is in the wrong section. Check the installed package version and the current API docs; place global options under expect.toHaveScreenshot or pass them to the assertion.
A baseline update makes the failure disappear but the change is unclear. The reference was replaced without reviewing the visual change. Review the diff first, then update snapshots only for an intentional UI change.

8. Performance, reliability, and cost

Screenshot comparisons require browser capture and image comparison, so the practical runtime depends on page loading, capture stability, image dimensions, and the number of assertions. Keep tests focused on representative states, and avoid repeatedly capturing the same unchanged page without a reason. The two-consecutive-capture stability check is useful for reliability, but it adds capture work and cannot correct environment differences.

For reliable CI results, pin the browser and runtime environment used to generate and compare baselines, use deterministic test data, and control animation and volatile regions. Treat snapshot files as code review artifacts: a tolerance change and a baseline update both deserve review because either can make a visual regression easier to miss.

There is no separate Playwright tolerance price specified in the cited documentation. The relevant project costs are the compute time used by your test environment and the storage and review effort for baselines; measure those in your own setup rather than assuming a universal benchmark.

9. Or skip the browser setup

If your goal is to capture a website image rather than maintain an automated visual regression test, ScreenshotNeo provides a one-request screenshot API. It returns PNG, JPEG, WebP, or PDF, and the API documentation covers its 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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
  • Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks and CAPTCHAs, blank pages, failed loads, timeouts, and cache hits are not billed. Response headers identify the page verdict and billing status.
  • An MCP server gives AI agents tools for screenshots, page information, and PDF capture.
  • The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.

Sign up for 1,000 free screenshots a month, with no card required.

10. FAQ

What is the default Playwright screenshot threshold?

The pixel comparison threshold defaults to 0.2. It controls per-pixel color sensitivity, not the number of pixels allowed to differ.

Can I set a tolerance globally and override it for one test?

Yes. Configure defaults under expect.toHaveScreenshot and pass assertion options to a particular toHaveScreenshot() call when that test needs a different policy.

Does a passing screenshot assertion prove the page is identical?

No. It means the capture passed the configured comparison rules. Any tolerated differences, masks, or environment-specific rendering differences limit what the assertion verifies.

Should I update snapshots automatically in CI?

Update them only after reviewing an intentional UI change. Unreviewed baseline replacement can turn a regression into the new expected image.

Sources