← Back to Playwright Mastery
Advanced18 min read

Visual Regression Testing

Catch UI regressions with screenshot comparison and snapshot thresholds.

Screenshot Assertions

expect(page).toHaveScreenshot() and expect(locator).toHaveScreenshot() compare against golden baseline on disk. First run creates baseline; subsequent runs diff pixel-by-pixel.

Configure snapshotPathTemplate for organized baseline folder structure per project/browser.

await expect(page).toHaveScreenshot("dashboard.png");
await expect(page.getByTestId("chart")).toHaveScreenshot();

Thresholds and Stability

maxDiffPixels and maxDiffPixelRatio tolerate anti-aliasing and font rendering variance. mask clips dynamic regions—clock, ads, animated GIF.

Disable animations in test env CSS for stable captures: prefers-reduced-motion or utility class.

  • Run visual tests same viewport and device project consistently
  • WebKit and Chromium baselines differ—separate snapshots per project
  • Update snapshots deliberately: npx playwright test --update-snapshots
await expect(page).toHaveScreenshot({
  mask: [page.getByTestId("timestamp")],
  maxDiffPixelRatio: 0.02,
});

Full Page vs Component

Full page screenshots catch layout shifts; component screenshots isolate widgets. Combine with component testing for fast visual coverage.

Scroll fullPage: true for long pages—watch file size growth.

await page.goto("/marketing");
await expect(page).toHaveScreenshot({ fullPage: true });

CI Visual Testing

Store baselines in repo LFS if large. CI fails on diff; upload diff image artifact for reviewer. Docker consistent fonts reduce flakiness vs macOS local baselines.

Some teams use Percy or Chromatic external services—Playwright native snapshots zero vendor cost.

  • Review diff images in PR before approving snapshot update
  • Never bulk update snapshots without visual review
  • Separate visual spec job optional for faster unit+ E2E PR gate

When Visual Tests Help

Charts, maps, complex CSS grids, and marketing pages benefit. Pure logic forms better suited to role/text assertions.

Visual tests complement not replace functional E2E—button can look correct and still fail click handler.

// Combine functional + visual
await page.getByRole("button", { name: "Submit" }).click();
await expect(page.getByText("Success")).toBeVisible();
await expect(page).toHaveScreenshot("success-state.png");

Get In Touch


Ready to discuss your next project? Drop me a message.