Visual Regression Testing
Catch unintended UI changes with visual regression tests - Chromatic, Percy, Playwright screenshot comparison, and integrating visual tests in CI.
skill: Visual Regression Testing category: Engineering tags: [Visual Testing, Chromatic, Percy, Storybook, Screenshot Testing] readingTime: 3
Visual regression testing catches the bugs that automated functional tests miss - the button that's technically clickable but has 0px height, the modal that appears behind other content, the text that overflows its container on a slightly different viewport. Pixel comparison is the only way to catch these reliably.
Workflow
- Set up Chromatic with StorybookChromatic is the easiest visual regression solution for Storybook - it captures screenshots of every component story, compares them to the approved baseline, and blocks PRs with unreviewed changes.
- Use Playwright for page-level screenshotsChromatic tests individual components; Playwright screenshot tests verify entire page layouts, including cross-component interactions and responsive behavior at different viewports.
- Manage baseline approvals and driftVisual regression tests require an approved baseline. Without a workflow for baseline updates, tests become noisy (too many false positives) and are disabled.
- Integrate visual tests in CI gateVisual tests in CI block PRs with regressions. The key is balancing sensitivity (catch real bugs) with noise (don't block PRs on rendering differences).
- Optimize for speed and reliabilityVisual tests are slower than functional tests. Parallelization, scoping to changed components, and intelligent baseline management keep visual tests under 5 minutes in CI.
Output Format
Visual regression: Chromatic with Storybook for components + Playwright screenshots for pages, maxDiffPixelRatio:0.01 tolerance, mask dynamic content, always run on same Docker image for consistency, and artifact upload on failure.
Chromatic: chromaui/action + CHROMATIC_PROJECT_TOKEN + exitOnceUploaded
Playwright: expect(page).toHaveScreenshot("name.png", { maxDiffPixelRatio:0.01, animations:"disabled" })
Mask: { mask: [page.locator(".timestamp")] } for dynamic content
Update: npx playwright test --update-snapshots -- --grep "component"
CI: always ubuntu container image for cross-machine consistencyExamples
Edge Cases & Gotchas
- Flaky screenshots from animations - CSS animations capture at different frame positions each run. Always set animations: "disabled" in Playwright screenshot options. For Lottie or Canvas animations, wait for a stable frame before capture.
- Font rendering differences - System fonts render differently across OS versions. Use web fonts (loaded from same URL) or embed fonts in your test container to ensure consistent rendering.
- Baseline update workflow - When multiple developers update baselines independently, git merge conflicts occur on screenshot binary files. Establish a convention: one PR per visual change, baseline updates in separate commits.
- Testing dark mode visually - Test both light and dark mode: await page.emulateMedia({ colorScheme: "dark" }) before taking screenshots. Maintain separate baseline sets for each theme.
When to Break These Rules
- Skip visual regression for B2B dashboards where data varies - masking all data effectively tests nothing but the layout chrome. Use component-level Chromatic tests for design system instead.
- Skip visual regression early in a project when the UI is changing rapidly - the overhead of updating baselines on every PR slows development. Add visual tests when the design stabilizes.
Reference Files
https://www.chromatic.com/docs/https://playwright.dev/docs/screenshots