---
title: "Testing Storybook"
description: "Storybook is both a component renderer and a documentation application."
canonical: storybook-tests.html
navigation-title: "Storybook tests"
---

<a id="testing-storybook"></a>

# Testing Storybook

- [Every story is a test](#every-story-is-a-test)
- [Test the shipped build](#test-the-shipped-build)
- [Judge with axe on purpose](#judge-with-axe-on-purpose)
- [Use a complete theme](#use-a-complete-theme)

Storybook is both a component renderer and a documentation application. A
story is a rendered component, and every story is a test. The sidebar, the
toolbar and the documentation chrome around it are a second surface, and a
story proves nothing about them. So the suite has two runners, and each
opens what the other cannot.

<a id="every-story-is-a-test"></a>

## Every story is a test

`@storybook/addon-vitest` turns each story into a Vitest test, and the
same run starts from the Storybook sidebar. One project, because the
addon starts one and names it after the config directory. A story renders
once and the verdict runs in both themes. The switch sets `data-theme`
on `<html>` and nothing else, so no second mount is necessary. A colour
that fails in one theme names it.

No Storybook build stands in front of the run. Vitest serves the sources
through Vite, in one Chromium, a frame per file.

What a story owes is in `.storybook/preview.ts` and
`.storybook/vitest.setup.ts`. It renders something, and prints no
warning and no error. A Specimen story is static markup drawn from the
system. And axe finds no serious or critical violation in it.

The `.test.ts` files under `tests/` mount a story, or write markup into
the frame. Then they ask what no story can answer on its own. What a press
does, what a form sends, where a mark stands after a scroll.

<a id="test-the-shipped-build"></a>

## Test the shipped build

The specs under `tests/` that Playwright runs open a server: the built
Storybook, the rendered site, the drop-in a consumer copies.
`tests/manager.spec.ts` opens the Storybook root, not `/iframe.html`,
waits for the explorer tree, checks the Soul title and fails on page or
console errors. It also chooses a viewport through the toolbar and checks
that the preview responds. It reads the index for every component's page,
and changes a control on the canvas and on the docs page. A story test
cannot stand in for any of these, because it never loads the manager bundle.

`@storybook/addon-a11y` stays in the build. An addon out only for tests
assembles a second surface and leaves the published one without a test.

<a id="judge-with-axe-on-purpose"></a>

## Judge with axe on purpose

The addon reports every violation in the panel and fails on none of them.
The suite holds a story to a line the addon has not: only a serious or
critical violation fails it. The specimens draw states no automated pass
can interpret, and a fail on `minor` trains everyone to ignore the run.
So under Vitest `.storybook/preview.ts` runs axe itself
in `afterEach`, and turns the addon's run off with `parameters.a11y`.
The specimen's own annotation layer, the `.spec-*` classes, stays out of
the context: it never ships to a product.

Coverage measures the frames, and the floor in `vitest.config.ts` holds
it. What only Node runs — the static renderer, the boot line — stays out
of the measure; `ssr`, `parity` and the drop-in's spec hold those.

The build keeps `light-dark()` whole. Lowered for older browsers, every
token resolves at the root, and a mode forced on a subtree forces nothing.
The suite reads the sources, so a build that lowered them proved less than
the run did. `.storybook/main.ts` sets the target.

<a id="use-a-complete-theme"></a>

## Use a complete theme

`create()` from `storybook/theming/create` makes the Storybook manager
theme. Storybook expects that complete theme. A partial object can omit
colours the manager uses and crash it before it can draw an error surface.
Customise the fields you pass to `create()`. Do not replace its result
with a hand-written object.
