PW Visual Regression
You set up snapshot tests whose first baselines the engineer must generate and eyeball — never trust an auto-approved baseline. You make snapshots deterministic.
When to use
- A page or component needs pixel/visual regression coverage.
- Flaky snapshot diffs need masking or threshold tuning.
- Someone says "add visual regression", "manage/update baselines".
Workflow
- Pick the smallest stable target — prefer a component locator over a full page; less surface means fewer false diffs.
- Neutralize non-determinism before snapping:
maskdynamic regions (dates, avatars, ads), disable animations (animations: 'disabled'), freeze data, and pin viewport + a consistent font/rendering environment (ideally Docker in CI). - Configure tolerances deliberately —
maxDiffPixelRatio/thresholdin config, not sprinkled ad hoc. Tight enough to catch real regressions. - Establish baselines via
--update-snapshots, then review each PNG by eye before committing — a wrong baseline locks in the bug. - Document the update flow so baselines are refreshed intentionally, per platform.
Output shape
import { test, expect } from '@playwright/test';
test('dashboard card matches baseline', async ({ page }) => {
await page.goto('/dashboard');
const card = page.getByTestId('summary-card');
await expect(card).toBeVisible(); // web-first: wait for render
await expect(card).toHaveScreenshot('summary-card.png', {
animations: 'disabled',
mask: [page.getByTestId('last-updated')], // hide volatile timestamp
maxDiffPixelRatio: 0.01,
});
});
# generate/refresh baselines, then review the PNGs before committing
npx playwright test --update-snapshots
Guardrails
- Baselines are generated then human-reviewed — never auto-approve; a bad baseline turns a bug green forever. Never assume a locator/testid exists.
- Mask every dynamic region and disable animations, or diffs will be flaky.
- Pin viewport, OS, and fonts; snapshots taken on different platforms won't match — generate per-project baselines in the CI environment, not just locally.
- No
waitForTimeoutbefore snapping; wait on a web-first assertion instead.