All docs
Recipes1 min readUpdated

Fix flaky visual tests

A visual test should only diff when the UI really changed. Every flaky diff traces to run-to-run variation you can eliminate at capture time.

A flaky diff comes back changed when nothing actually changed. The screenshot is not lying - it faithfully caught something that moves between runs, like the clock or an animation frame. Remove that source and the flake goes with it.

Common causes of flaky visual tests

  • The clock - relative timestamps like "2 hours ago", or a date rendered from Date.now().
  • Animations - an infinite CSS or JS animation caught mid-frame at a different point each run.
  • Live data - a component that fetches a real API renders different content every time.
  • Randomness - Math.random, generated ids, shuffled orders.
  • Third-party widgets - maps, embeds, ad slots that load their own moving content.
  • Web fonts - text captured before the font swaps in reflows on the next run.

Fix them at record time

  • Freeze the clock to a fixed instant so relative-time labels are identical every run.
  • Disable or pause animations for the capture.
  • Feed components fixture data instead of a live fetch.
  • Seed or stub any randomness to a fixed value.
  • Stub third-party embeds behind a static placeholder.
  • Wait for fonts to load before the screenshot is taken.

Storybook vs real pages

For Storybook, control the variation inside the story - fixed props, frozen time, fixture data. For real pages driven by Playwright, archive-replay bakes the page in at record time, so a live API or a feature flag cannot shift it on replay - but you still freeze the clock and stub moving widgets before you record.

The full symptom-to-fix checklists live in the Storybook determinism and Playwright determinism skills, so your agent can apply them for you.

Visual testing for agents

UI Verify captures your UI on every pull request and an AI judge tells an intended change from a real regression. See how it works.

Get started