Files
2026-05-27 23:29:53 +01:00

2.9 KiB
Raw Permalink Blame History

Architecture

Two cooperating helpers share one threshold, one viewport, and one font/animation discipline so the e2e and Storybook baselines stay comparable.

Adapter choice

  • e2e path: Playwright's built-in expect(page).toHaveScreenshot() — already in the dependency closure, supports per-OS baselines natively, threshold semantics (maxDiffPixelRatio, maxDiffPixels) are well documented. No pixelmatch direct dep needed.
  • Storybook path: @storybook/test-runner is the canonical Storybook→Playwright bridge. It exposes preVisit / postVisit hooks where we capture screenshots using the same Playwright Page instance, so the comparator and threshold match the e2e path bit-for-bit.

A pure-Node pixelmatch helper was rejected: it would fork the comparator, drift in floating-point handling between OSes, and double the surface to maintain.

Default thresholds

  • maxDiffPixelRatio: 0.005 (0.5%). Tighter ratios (e.g. 0.001) produce noise from sub-pixel font rendering on different GPU drivers.
  • animations: 'disabled', caret: 'hide', scale: 'css'.
  • Viewport: 1280×720 default. Stories pin to the same default unless the story sets an explicit parameters.viewport.
  • prefers-reduced-motion: reduce forced via page.emulateMedia.

OS matrix

Baselines are stored per platform: __snapshots__/<linux|darwin|win32>/.... CI MUST run at least the platforms it intends to gate on. A platform with no baseline directory MUST skip the comparison (no implicit pass) and emit a warning.

Baseline-update workflow

  1. Developer runs bunx turbo test --filter=<pkg> -- --update-snapshots locally.
  2. Diff is committed in a separate, review-tagged commit prefixed chore(visual): refresh baselines — <reason>. Squashing into feature commits is forbidden.
  3. PR reviewer compares before/after PNGs in the diff view; CI re-runs the comparator on the new baselines to catch accidental cross-platform drift.

Determinism levers

  • Fonts: await document.fonts.ready after networkidle. Web fonts that load lazily are the #1 source of flake.
  • Color scheme: stories with parameters.colorScheme === 'dark' toggle via page.emulateMedia({ colorScheme: 'dark' }) before capture.
  • Time: any time-display surfaces under test must already accept an injected Clock per openspec/project.md; visual specs SHALL pass a FakeClock set to a fixed instant.

Error model

VisualRegressionError extends TestingE2eError for the e2e path, StorybookVisualError extends TestingDomError for the Storybook path. Each carries { code, cause?, context: { baselinePath, actualPath, diffPath, pixelRatio } }.

  • Release-readiness gate: ../enterprise-testbed-verification/proposal.md.
  • Audit driver: /mnt/s/web/genesis/docs/superpowers/specs/2026-04-25-test-suite-audit.md §2 row 2.
  • Project rules: /mnt/s/web/genesis/CLAUDE.md "Web App & Page Verification".