Set up visual regression testing with Playwright screenshots
Build a visual regression test suite using Playwright's built-in screenshot comparison. Capture baseline screenshots for a web application's key views, introduce a deliberate visual change, verify the test detects it, and configure the suite to run in CI with a tolerance threshold and a diff image artifact on failure.
Why this matters
Functional tests verify that a button works; visual regression tests verify that it still looks right. CSS changes, dependency upgrades, and font loading issues cause visual regressions that no functional test catches. A screenshot-based regression suite is the closest thing to automated visual QA; it does not replace human review, but it catches the regressions nobody would notice until a user complained.
Before you start
- Node.js with @playwright/test installed
- A web application to test; any locally runnable app works
- Git, for committing baseline screenshots
- Basic Playwright test writing experience
Step-by-step guide
- 1
Write the first screenshot test
Use toHaveScreenshot() in a Playwright test. Run it once; Playwright creates the baseline PNG in __screenshots__. Commit it to the repository. Run again; the test should pass. This is the full flow: generate baseline, commit, assert on subsequent runs.
- 2
Capture baselines for 5 key views
Write screenshot tests for: homepage, a listing page, a detail page, a form in empty state, and a form showing a validation error. Run all 5 once to generate baselines. Name them clearly; the file name is what you read in the CI failure log.
- 3
Set a pixel diff threshold
Add { maxDiffPixels: 100 } to toHaveScreenshot() for views with dynamic content (timestamps, animated elements). For static views, use maxDiffPixelRatio: 0 to catch any change. Tight thresholds generate false positives; loose thresholds miss real regressions; calibrate per view.
- 4
Introduce a deliberate regression
Change a CSS value (background colour, font size, spacing) in the app. Run the screenshot tests. At least one should fail with a diff image. Open the diff image; the changed pixels are highlighted in red. Verify you can see the change clearly. Revert and verify all tests pass again.
- 5
Handle dynamic content
Some views contain content that changes on every load (timestamps, random data). Use page.evaluate to freeze time before screenshotting, or mask the dynamic region with toHaveScreenshot({ mask: [page.locator('.timestamp')] }). Run the test 3 times and verify it is stable.
- 6
Add to CI
Configure the Playwright test to run in GitHub Actions. Store the baseline screenshots in the repository. On failure, use the actions/upload-artifact step to capture diff images as build artifacts. The workflow is: PR opens, CI runs, screenshot fails, diff image is attached, reviewer decides if the change is intentional.