Visual regression testing
Functional tests pass while a stylesheet change quietly shoves a button off
screen or turns label text the same color as its background. Pixels are the
only thing that catches pixels. Screenshot diffing works, but done naively it
buries you in false positives from fonts, animation, and antialiasing, so the
real craft here is controlling that flake rather than taking the picture.
Method
- Freeze everything nondeterministic before capture. Disable CSS
animations and transitions, stop videos, pin the clock, and stub any live
data. A blinking cursor or a relative timestamp will diff on every run and
train people to ignore diffs.
- Pin the rendering environment. Run captures in a fixed container with
one browser version and a set viewport and device pixel ratio. Fonts and
subpixel rendering differ across operating systems, so a baseline shot on
a laptop will fail in CI. Playwright and Docker together make this
repeatable.
- Snapshot components, not just whole pages. Capture a button, a card, a
modal in isolation with a tool like Storybook plus a runner. A component
diff points straight at the culprit; a full-page diff lights up on any
unrelated change above it.
- Set a per-pixel and total-difference threshold. Allow a small
antialiasing tolerance per pixel and a maximum changed-pixel fraction, for
example fail above 0.1% of pixels. Zero tolerance flakes constantly; a
loose threshold hides real one-pixel shifts. Tune it per component.
- Review diffs as approvals. When a shot changes, open the side-by-side
in Percy, Chromatic, or reg-suit and decide whether the change is intended
before promoting it to the new baseline. Rubber-stamping the update is how
a regression becomes the reference image.
- Mask known-dynamic regions. Blackout or ignore areas that legitimately
vary, such as an avatar, an ad slot, or a live counter, so the rest of the
frame stays assertable instead of the whole shot being written off.
- Fail the build and publish the diff image. Wire the check into CI so a
drift blocks merge, and attach the highlighted diff to the run so the
reviewer sees what moved without reproducing it locally.
Checks
- Does re-running the suite with no code change produce zero diffs, every
time?
- When a diff appears, can a reviewer see exactly which pixels moved and
approve or reject in one click?
- Are baseline images committed and their changes reviewed like code, not
silently overwritten?
Boundaries
Screenshot diffing proves the UI looks unchanged, not that it works or is
accessible: pair it with functional tests and an accessibility review. The
approve-or-reject workflow is shared with approval-testing, which handles
text output the same way. Whether a rendered result looks right in the first
place remains a design judgment.
1---2name: visual-regression-testing3description: Catch unintended UI changes by diffing rendered screenshots against approved baselines with tuned thresholds and flake control. Use when protecting a component or page from visual drift across code changes.4---56# Visual regression testing78Functional tests pass while a stylesheet change quietly shoves a button off9screen or turns label text the same color as its background. Pixels are the10only thing that catches pixels. Screenshot diffing works, but done naively it11buries you in false positives from fonts, animation, and antialiasing, so the12real craft here is controlling that flake rather than taking the picture.1314## Method15161. **Freeze everything nondeterministic before capture.** Disable CSS17 animations and transitions, stop videos, pin the clock, and stub any live18 data. A blinking cursor or a relative timestamp will diff on every run and19 train people to ignore diffs.202. **Pin the rendering environment.** Run captures in a fixed container with21 one browser version and a set viewport and device pixel ratio. Fonts and22 subpixel rendering differ across operating systems, so a baseline shot on23 a laptop will fail in CI. Playwright and Docker together make this24 repeatable.253. **Snapshot components, not just whole pages.** Capture a button, a card, a26 modal in isolation with a tool like Storybook plus a runner. A component27 diff points straight at the culprit; a full-page diff lights up on any28 unrelated change above it.294. **Set a per-pixel and total-difference threshold.** Allow a small30 antialiasing tolerance per pixel and a maximum changed-pixel fraction, for31 example fail above 0.1% of pixels. Zero tolerance flakes constantly; a32 loose threshold hides real one-pixel shifts. Tune it per component.335. **Review diffs as approvals.** When a shot changes, open the side-by-side34 in Percy, Chromatic, or reg-suit and decide whether the change is intended35 before promoting it to the new baseline. Rubber-stamping the update is how36 a regression becomes the reference image.376. **Mask known-dynamic regions.** Blackout or ignore areas that legitimately38 vary, such as an avatar, an ad slot, or a live counter, so the rest of the39 frame stays assertable instead of the whole shot being written off.407. **Fail the build and publish the diff image.** Wire the check into CI so a41 drift blocks merge, and attach the highlighted diff to the run so the42 reviewer sees what moved without reproducing it locally.4344## Checks4546- Does re-running the suite with no code change produce zero diffs, every47 time?48- When a diff appears, can a reviewer see exactly which pixels moved and49 approve or reject in one click?50- Are baseline images committed and their changes reviewed like code, not51 silently overwritten?5253## Boundaries5455Screenshot diffing proves the UI looks unchanged, not that it works or is56accessible: pair it with functional tests and an accessibility review. The57approve-or-reject workflow is shared with approval-testing, which handles58text output the same way. Whether a rendered result looks right in the first59place remains a design judgment.