Tags: web-dev concept

Visual Regression Testing

Date: 2026-08-17


Screenshotting components or pages and comparing against approved baselines. It catches the class of bug no other test sees — a CSS change breaking a layout three components away — and it fails constantly for reasons nobody cares about unless it’s tightly scoped.


Visual regression testing captures rendered output as images and compares them pixel by pixel against stored baselines, flagging differences for a human to approve or reject.

What only this catches

a CSS change that broke a component
  three files away
a font swap shifting every layout
a container query firing at the wrong
  width
a dark-mode variant nobody rendered
  — Theming
a long product name breaking a card
  — Component States
an icon that stopped rendering

See: Theming · Component States

None of these fail a functional test. The button works, the text is present, the accessible name is correct — it simply looks wrong, and only a human or a pixel comparison notices.

The false-positive problem

This is the defining weakness, and it decides whether the practice survives.

DIFFS THAT AREN'T BUGS
  font rendering across OS and browser
  anti-aliasing
  scrollbar width
  animations captured mid-frame
  dates, prices, or any dynamic content
  image loading timing
  cursor position and focus rings

A suite failing on every run gets approved without looking, which is worse than not having it — someone clicks “accept all” and a real regression lands with the noise.

Making it reliable

RENDER IN A CONTAINER
  identical OS, fonts and browser
  every time
  ← the single biggest factor
  — Containers

DISABLE ANIMATION
  prefers-reduced-motion, or a global
  transition override

FREEZE DYNAMIC CONTENT
  fixed dates, fixed prices, seeded
  data — Test Data

WAIT FOR FONTS AND IMAGES
  document.fonts.ready

MASK VOLATILE REGIONS
  exclude a timestamp rather than
  fighting it

SET A SENSIBLE THRESHOLD
  a small pixel tolerance, not zero

See: Containers · Test Data

Running in a container is what makes this work at all. Baselines captured on a developer’s Mac and compared against Linux CI will differ on every text-containing component, permanently.

Scope it to components, not pages

COMPONENT-LEVEL
  → a Button in 8 states
  → small images, fast, stable
  → a failure localises immediately
  ← the right default

FULL-PAGE
  → one change fails everything
  → diffs are large and hard to read
  → slow
  ← a handful of key pages, at most

Component-level visual testing over a design system is the strongest use of this technique — it’s exactly the case where consistency is the product and where a regression propagates everywhere — Design Systems, Component Documentation.

Generating the shots from the same stories that document the component means no extra authoring — the variant matrix you built for documentation is the test suite.

The approval workflow

change made
  ↓
diffs generated
  ↓
a HUMAN reviews each one
  ↓
intended?   → approve, becomes the
              new baseline
unintended? → fix

The human step is the whole method. A visual test cannot know whether a difference is a bug or the change you just made — which makes it a review aid rather than a pass/fail gate, and means it must be fast enough to review comfortably.

Baselines must be committed and reviewable. Binary files in git are awkward; hosted services store them instead, which is most of what you’re paying for.

Where it’s not worth it

  • Pages with heavy dynamic content — a category page whose products change
  • Rapidly-changing designs. During an active redesign every diff is intended, and the suite is pure cost
  • As a substitute for accessibility testing. A screenshot proves nothing about contrast being sufficient, focus being visible, or the accessible name being right — Accessibility Testing
  • Where nobody will review the diffs. Then it’s noise generating a monthly bill

The honest position

High value on a design system, low value on application pages. The components are stable, reused everywhere, and the exact case where a small CSS change has consequences nobody predicted.

Start with the component library and stop there unless a specific page has repeatedly broken.