# Visual Regression Testing a Component Registry (/blog/visual-regression-component-registry)

Author: Ducksss
Published: 2026-07-14T00:00:00.000Z



A component registry can compile while its cards overflow, headings wrap badly, or mobile order
breaks. Visual regression tests turn representative rendering into a reviewed contract. They are
most useful when capture is stable, baselines are platform-aware, and missing coverage cannot hide
behind a silent skip.

This repository renders each demo twin on a chrome-free route, captures it through Playwright, and
compares the result with committed baselines. Integration tests separately keep every installable
source class token present in the preview.

<BlogFigure src="/blog/visual-regression-component-registry/figure-01-regression-pipeline.svg" alt="A zero-baseline platform intentionally skipping visual cases until its first image is minted, followed by a minted-platform coverage gate requiring every component slug" caption="The bootstrap exception is narrow: a zero-baseline platform skips intentionally; once any platform baseline exists, all current slugs are required." />

## Capture one component, not the whole site [#capture-one-component-not-the-whole-site]

The route `/components/preview/<slug>` removes navigation and catalog chrome. It renders one demo twin
inside a controlled container. That isolation reduces unrelated diffs and gives the component's own
responsive breakpoints a real viewport.

Capturing a CSS-scaled specimen inside the catalog would be misleading. Tailwind media queries respond
to the browser viewport, not the visual width created by transform scaling. An iframe or dedicated
page gives the demo an honest layout environment.

The [demo-twin article](/blog/demo-twins) explains why the preview uses site-only fixtures while
retaining source class tokens.

## Control sources of nondeterminism [#control-sources-of-nondeterminism]

Use fixed viewport sizes, the same browser project, stable fonts, deterministic fixture content, and
settled assets. Disable or finish animation in capture mode. Wait for fonts and images rather than a
random timeout. Avoid current timestamps, random IDs rendered as text, live network content, and
environment-specific data.

Motion behavior still needs its own tests. Freezing motion for a screenshot is appropriate only when
browser coverage separately proves reduced-motion and interaction paths. The
[motion guide](/blog/motion-without-performance-cost) describes that division.

Keep expected rendering light-themed or dark-themed according to the product contract. This site is
forced light; terminal cards are intentional dark surfaces, not evidence of theme switching.

## Use per-platform baselines honestly [#use-per-platform-baselines-honestly]

Fonts, rasterization, and browser rendering can differ across macOS and Linux. A local macOS image
should not silently become the expected Linux CI baseline. Name or organize snapshots by project and
platform so comparisons remain meaningful.

Linux baselines are generated in the CI renderer through a dedicated workflow. That avoids approving
a locally simulated image the gate never actually produces. Local baselines remain useful for quick
iteration and visual review on the developer's platform.

Do not increase pixel thresholds until a real regression disappears. First inspect whether the
difference comes from fonts, animation, assets, environment, or an intended design change.

## Make missing baselines visible [#make-missing-baselines-visible]

On a zero-baseline platform, cases intentionally skip while the renderer is being bootstrapped. That
exception ends as soon as its first baseline is minted. From then on, the platform is established,
and a new component must not ship without a visual contract while CI stays green.

A coverage guard enforces that boundary. Once any baseline exists for a platform, every current
component must have one. Missing coverage fails the gate with the slug. The contributor can then mint
the intended image through the documented workflow.

The count should derive from the component registry or site source of truth, not a second hand-written
list that can drift.

## Review diffs as product changes [#review-diffs-as-product-changes]

When a screenshot fails, open expected, actual, and diff images. Determine whether the change is:

* an accidental regression in source;
* an intentional design update;
* a demo-twin mismatch;
* a capture instability;
* a platform baseline that needs minting.

Fix accidental behavior in source and twin. For intentional changes, review responsive states and
related variants before updating snapshots. The baseline update belongs in the same pull request and
should be explained like any other product change.

Never regenerate all images merely to make the suite green. Broad churn can bury the one component
that moved unexpectedly.

## Pair pixels with semantic tests [#pair-pixels-with-semantic-tests]

A screenshot cannot prove heading order, accessible names, keyboard behavior, link destinations,
reduced-motion completeness, or data correctness. Browser assertions and accessibility scans cover
those facts. Integration tests cover registry, manifest, source, and demo contracts. TypeScript covers
generated prop relationships.

The useful suite is layered:

1. visual standards reject arbitrary design values in source;
2. class-token containment tests keep source tokens present in previews;
3. component snapshots detect rendered change;
4. page tests catch overflow and composition issues;
5. interaction and accessibility tests verify behavior.

The [testing contract in the docs](/docs/contributing) tells contributors which checks to run for a
component change.

## Keep the suite proportional [#keep-the-suite-proportional]

Capture one representative state per component at the catalog's standard viewport, then add focused
states only when they protect a real risk. Do not snapshot every possible copy value. Use browser
assertions for dynamic behavior and unit or integration tests for content transformations.

At page level, keep a desktop and mobile baseline for important shared layouts such as the catalog,
blog index, and article chrome. Those snapshots catch composition changes the isolated component
route cannot see.

Run the smallest affected visual case while iterating, then the full browser gate before shipping.

## Add a component with its baseline [#add-a-component-with-its-baseline]

A complete component contribution registers the demo twin and adds its visual case alongside source,
manifest, registry, docs, and installer tests. Start with an existing item to understand the route:

```bash
npx payload-components add feature-bento
```

Compare the installed source with the [Feature Bento docs](/docs/components/feature-bento) and preview.
If you find a flaky capture or uncovered platform case, contribute the minimal reproduction and
stabilization through the [contributing guide](/docs/contributing). A visual gate earns trust when it
makes change legible and missing evidence impossible to ignore.
