UI Verify
All docs
Core concepts2 min readUpdated

Skip unchanged stories and tests with --only-changed

Render only the stories or tests your pull request affects and carry the rest forward - the biggest lever on build time and per-snapshot cost.

On a large suite most pull requests touch a handful of components. Re-rendering all 1,000 stories or tests every time is slow, and on any per-snapshot tool it is expensive. --only-changed renders just the stories or tests your change can affect and carries the unchanged ones forward from their baseline.

bash
npx -y uiverify@1.4.0 upload --static-dir ./storybook-static --only-changed

It is the same flag on a Vitest archive - point it at your archive directory instead of the built Storybook:

bash
npx -y uiverify@1.4.0 upload --static-dir ./uiverify-archive --only-changed
--only-changed needs a module dependency graph in the bundle (preview-stats.json). Storybook only writes it when you build with --stats-json (npm run build-storybook -- --stats-json); a Vitest archive gets it automatically from @uiverify/vitest 1.1+. Without that file the flag safely no-ops and every story renders - so if skipping never seems to happen, this is why.

How it decides what changed

--only-changed walks your pull request's file changes against the module dependency graph and selects every story that imports a changed file, directly or transitively. Everything else is carried forward: its existing baseline is reused as this build's result, with no render and no diff. A carried snapshot still counts toward billing, but at a fifth of a rendered one, so a typical pull request bills a fraction of your full UI.

A passed UI Verify build showing 21 rendered and 103 carried forward, with no visual changes.
21 stories rendered, 103 carried forward from baseline - the same 124-screenshot coverage, a fraction of the work.

What triggers a full render?

  • No preview-stats.json in the build - on Storybook you did not pass --stats-json, so there is no graph to trace.
  • A change to a file many stories import - a global stylesheet, a design-token file, the Storybook preview.
  • A lockfile or dependency change, which can shift anything.
  • The first build on a branch, which has nothing to carry forward yet.

In each of those cases re-rendering everything is the correct, safe answer, so it does.

Does it work with Vitest and Playwright?

Vitest: yes. @uiverify/vitest 1.1+ writes the Vite module graph into the archive automatically, so --only-changed traces a changed file to the browser-mode tests that import it - directly or transitively - exactly as it does for Storybook stories, with nothing to configure. Playwright: no. A real page has no module graph relating source files to a screenshot, so those uploads always render what your tests captured. To keep Playwright builds fast and cheap, make the captures deterministic instead - see Fix flaky visual tests.

Is it safe? Could it skip a story that really changed?

A carried-forward story is one whose inputs your change did not touch, so its pixels should not have moved. The guarantee is only as good as the change detection, though, so treat any surprise as a signal: if you ever see a carried-forward story that clearly should have rendered, or a diff you cannot attribute to your own change, investigate the baseline rather than accepting it.

Skipping is one cost lever; writing fewer, denser stories is the other. See Reduce visual test snapshots and cost.

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