Playwright includes built-in screenshot comparison via toHaveScreenshot(). It's free, stores snapshots in your repository, and integrates naturally with your existing Playwright tests.
Setting Up Snapshots
No additional configuration is required beyond a standard Playwright setup. The snapshot assertion is available out of the box:
import { test, expect } from '@playwright/test'
test('homepage looks correct', async ({ page }) => {
await page.goto('/')
await expect(page).toHaveScreenshot('homepage.png')
})On the first run, Playwright saves a baseline screenshot. On subsequent runs, it compares the current screenshot against the baseline and fails if they differ beyond a threshold.
Generating Baselines
Run your tests with --update-snapshots to generate or update baselines:
npx playwright test --update-snapshotsThis creates snapshot files in a __snapshots__ directory next to your test file. Commit these files to your repository — they are the source of truth for what the UI should look like.
Handling Acceptable Differences
Screenshots can differ legitimately — different operating systems render fonts slightly differently, antialiasing varies, and animations may be at different frames. Playwright handles this with a pixel threshold:
await expect(page).toHaveScreenshot('homepage.png', {
maxDiffPixelRatio: 0.01 // Allow up to 1% pixel difference
})You can also mask dynamic regions (timestamps, user avatars, ads) that legitimately change between runs:
await expect(page).toHaveScreenshot('dashboard.png', {
mask: [page.locator('.timestamp'), page.locator('.user-avatar')]
})Component-Level Snapshots
Full-page screenshots are noisy — a change anywhere on the page causes a failure. Component-level snapshots are more focused and easier to maintain:
test('button component variants', async ({ page }) => {
await page.goto('/components/button')
const primaryButton = page.getByRole('button', { name: 'Primary' })
await expect(primaryButton).toHaveScreenshot('button-primary.png')
const secondaryButton = page.getByRole('button', { name: 'Secondary' })
await expect(secondaryButton).toHaveScreenshot('button-secondary.png')
})Snapshot Testing Strategy
Test what matters visually: Not every page needs snapshot tests. Prioritise pages and components where visual correctness is critical — landing pages, marketing components, design system components.
Keep snapshots in CI: Run snapshot tests in CI to catch regressions before merge. Developers update snapshots locally when making intentional changes.
Review snapshot diffs carefully: When a snapshot test fails, look at the diff image before deciding whether to update the baseline. A small diff might be a real regression; a large diff might be an intentional redesign.