EduFlow

Section 4 · Tooling and Delivery

Storybook, Visual Regression and Releases

22 min3 min readSpot the bug

Lesson at a glance

Turn Storybook stories into the test suite for every component state, catch unintended visual changes with screenshot comparison across themes, and run releases through a CI pipeline that checks before it publishes.

  1. 1Stories are test cases
  2. 2Visual regression testing
  3. 3The release pipeline

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

Northwind’s 4.2.0 release changed one line: the line-height token for body text, from 1.5 to 1.45. It looked fine in every story we checked. It also pushed the last row of the dispatch app’s route table below the fold on the most common laptop resolution, in compact density, in dark mode. Nobody had looked at that combination. Since then, the rule has been simple: machines look at every combination; humans look at the differences.

Stories are test cases

A Storybook story renders a component in one state with specific props. You wrote stories for documentation in the last section; the same stories are the backbone of testing:

  • Rendering: every story must render without errors.
  • Interaction: a story can have a play function that clicks, types and asserts, such as “pressing Escape closes the Dialog and returns focus to the trigger”.
  • Accessibility: the accessibility addon runs axe-core checks against each story and reports violations such as missing labels or failing contrast.
  • Visual: each story is screenshotted and compared with an approved baseline.

Recent Storybook versions can run stories as tests through a Vitest integration (@storybook/addon-vitest), so the same files run in the Storybook UI, in the terminal and in CI. Northwind requires one story per cell of the state matrix from the accessibility lesson. That rule gives you test coverage almost for free, because the work of designing the states was already done.

Visual regression testing

A visual regression test renders a story, takes a screenshot and compares it pixel by pixel with a stored baseline. If they differ beyond a threshold, the test fails and shows the diff. A human then decides: if the change was intended, accept it as the new baseline; if not, fix the code.

Teams typically choose one of two setups. A hosted service such as Chromatic, made by Storybook’s maintainers, renders stories in the cloud and gives reviewers a web UI for accepting diffs. Or you run screenshots yourself with Playwright:

tests/visual.spec.ts
import { test, expect } from '@playwright/test';

const stories = ['components-button--primary', 'components-button--danger', 'components-dialog--confirm'];
const themes = ['light', 'dark'];
const densities = ['comfortable', 'compact'];

for (const id of stories) {
  for (const theme of themes) {
    for (const density of densities) {
      test(`${id} ${theme} ${density}`, async ({ page }) => {
        await page.goto(`/iframe.html?id=${id}&viewMode=story`);
        await page.evaluate(([t, d]) => {
          document.documentElement.dataset.theme = t;
          document.documentElement.dataset.density = d;
        }, [theme, density]);
        await expect(page).toHaveScreenshot(`${id}-${theme}-${density}.png`);
      });
    }
  }
}

Each story renders in every combination of theme and density, plus brand in Northwind’s real suite, which is how the line-height regression would have been caught. Playwright’s toHaveScreenshot disables CSS animations by default and retries until two consecutive screenshots match, which removes the most common sources of noise.

Visual tests also guard the token tiers. Northwind adds a Stylelint rule in the same CI job that rejects any reference to a primitive token, such as var(--nw-blue-600), inside component styles. The visual suite then confirms that the rule-abiding CSS still renders what designers approved.

The release pipeline

Testing only protects consumers if publishing can’t skip it. Northwind’s pipeline has two halves.

On every pull request: install with npm ci, run the token checks (naming, references, contrast), typecheck, lint, unit and story tests, accessibility checks and visual tests, and require a changeset file when a package changed. The pull request can’t merge until all of these pass and a core member approves the visual diffs.

On main: the Changesets action collects pending changesets into a “Version Packages” pull request that bumps versions and updates CHANGELOG.md. Merging that pull request runs the publish step:

.github/workflows/release.yml
name: Release
on:
  push:
    branches: [main]
jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write        # push version commits and tags
      pull-requests: write   # open the Version Packages pull request
      id-token: write        # npm trusted publishing
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
      - run: npm ci
      - run: npm run build
      - run: npm test
      - uses: changesets/action@v2
        with:
          publish-script: npm run release

Publishing happens last, only from main, only after build and tests. After the packages are out, the same workflow deploys the documentation site and the Storybook build, and the core team imports the released tokens into the Figma library.

In the exercise, you review a release workflow that can publish broken code. That completes the delivery section; next, you scale the system to forty teams.

Key takeaways

  • A story is a documented component state, and the same story can drive interaction tests, accessibility checks and visual snapshots.
  • Visual regression tests compare screenshots against approved baselines, so unintended changes show up as diffs a human accepts or rejects.
  • Render every component in every theme combination, because the corners you never look at are the ones that break.
  • A release pipeline checks, then versions, then publishes; nothing is published from a branch that hasn’t passed every check.

Further reading

Check your understanding

3 questions

Question 1 of 3

A visual test fails on 46 stories after a pull request that changed only the Dialog’s padding token. What should the reviewer do first?

Question 2 of 3

Visual tests pass on a developer’s laptop but fail in CI with tiny differences in text edges. What’s the most likely cause and fix?

Question 3 of 3

Where should npm publish run in a design system’s release workflow?

Practice

Spot the bug

Stop the workflow from publishing broken code

This GitHub Actions workflow is meant to publish @northwind/ui to npm after changes are merged and tested. As written, it can put untested, unreviewed code in front of all 40 teams. Select the line or lines that cause that.

Select the line(s) you think are wrong, then check.