Visual bugs are real bugs. A layout shift, a colour regression, a truncated label — these reach users and damage trust, yet they're invisible to functional tests. Snapshot testing fills this gap.
How Playwright Visual Testing Works
Playwright captures a screenshot of a component or page and compares it pixel-by-pixel against a stored baseline. If the diff exceeds a threshold, the test fails.
test('homepage hero looks correct', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('hero.png', { maxDiffPixels: 100 });
});
On first run, the screenshot is saved as the baseline. Subsequent runs compare against it.
Setting Baselines
Run once with --update-snapshots to generate your initial baselines. Commit these to source control — they're the source of truth for what the UI should look like.
npx playwright test --update-snapshots
Handling Dynamic Content
Dynamic content — dates, user names, animated elements — will cause false failures. Mask them:
await expect(page).toHaveScreenshot('dashboard.png', {
mask: [page.getByTestId('user-greeting'), page.getByTestId('live-clock')]
});
CI Integration
Visual tests are slow and should run on a dedicated step, not blocking every commit:
- name: Visual Regression Tests
run: npx playwright test tests/visual/
continue-on-error: true # warn, don't block initially
As confidence grows, move this to a blocking gate.
Component-Level vs Page-Level
Page-level snapshots are brittle — any content change anywhere fails the test. Prefer component-level snapshots that isolate what you're testing.
Conclusion
Visual regression testing catches a category of bugs that no amount of functional testing will find. Set it up once, commit your baselines, and let CI guard your UI automatically.