Skip to content

Visual Testing in Playwright

Playwright includes built-in visual regression via toHaveScreenshot() and toMatchSnapshot(). Compare current renders against committed baseline images and fail when pixels differ beyond a threshold.

Screenshot Assertions

First run generates baseline PNGs stored in a *-snapshots/ folder next to your spec. Subsequent runs pixel-compare against baselines. Update baselines intentionally with --update-snapshots after verified UI changes.

Mask dynamic regions (timestamps, ads, avatars) with mask: [locator] so immaterial differences do not fail tests.

test('homepage looks correct', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveScreenshot('homepage.png', {
    maxDiffPixels: 100,
    mask: [page.locator('.live-clock')],
  });
});

Commit snapshot PNGs to git — they are the expected visual truth.

Snapshot Commands

await expect(page).toHaveScreenshot('name.png')
await expect(locator).toHaveScreenshot()
npx playwright test --update-snapshots  # refresh baselines
  • Baselines live in spec-file-name-snapshots/ directories.
  • maxDiffPixels and threshold tune sensitivity.
  • mask hides dynamic elements from comparison.
  • Run visual tests in consistent viewport and project settings.

Visual Testing Reference

API and CLI for snapshots.

API / Flag Purpose
toHaveScreenshot() Full page or viewport snapshot
locator.toHaveScreenshot() Single component snapshot
--update-snapshots Regenerate baselines after UI change
maxDiffPixels Allow small pixel drift
mask Exclude dynamic regions
Project-specific dirs Separate baselines per browser

Per-Browser Baselines

Fonts and anti-aliasing differ across Chromium, Firefox, and WebKit. Store separate snapshot directories per project or run visual tests only on one canonical browser for stability.

Updating Baselines in PRs

When UI changes intentionally, run --update-snapshots locally, commit new PNGs in the PR, and require design review on snapshot diffs — treat them like code changes.

Common Mistakes

  • Running visual tests across all browsers without separate baselines — constant false positives.
  • Not masking clocks, random IDs, or animation frames.
  • Updating snapshots blindly without reviewing pixel diffs.
  • Different viewport sizes locally vs CI causing mismatches.

Key Takeaways

  • toHaveScreenshot() compares against committed baseline PNGs.
  • Use mask for dynamic content and tune maxDiffPixels.
  • Update baselines with --update-snapshots after intentional UI changes.
  • Consider one canonical browser for visual tests to reduce noise.

Pro Tip

Stabilize pages before snapshots: await expect(locator).toBeVisible() and wait for fonts with document.fonts.ready if text shifts.