How to Test Storybook Stories in Different Modes
Choose the right Storybook test for rendering, interaction, accessibility, and visual changes, then run a practical mix locally and in CI.
Use render or component tests to check that a story mounts, interaction tests to verify user behavior, accessibility checks to find some automated rule violations, and visual tests to catch appearance changes. Reuse stories in unit or end-to-end tests when you need to test them in a different environment or as part of a larger workflow. No single mode proves that a component is correct in every respect.
For most Vite-based Storybook projects, start by checking whether the current Vitest addon supports your framework and installed versions. The documented Storybook test-runner remains an option for some projects, but official support has ended. Check the version-specific docs before choosing a runner.
What each testing mode tells you
| Mode | Question it answers | Use it for | It does not prove |
|---|---|---|---|
| Render/component | Can this story render in its configured state? | Smoke checking stories and static component states | Correct behavior after user input or application integration |
| Interaction | Does the component respond correctly to user actions? | Important flows such as submitting a form or opening a menu | Every possible interaction or appearance across browsers |
| Accessibility | Did automated rules find issues in the rendered DOM? | Finding some common accessibility violations early | Full accessibility, usability, or standards compliance |
| Visual | Does the rendered story differ from an accepted image baseline? | Appearance-sensitive components and regression review | Correct behavior or accessibility |
| Markup snapshot | Did rendered markup change from a stored baseline? | Selected cases where markup changes are meaningful | Pixel-level visual similarity |
| Unit or end-to-end reuse | Does the story work in another test harness or application workflow? | Unit-level reuse or flows requiring the wider app stack | Every isolated state unless you explicitly cover it |
Storybook describes stories as test cases for UI components in their states and configurations. The testing overview covers component, visual, snapshot, unit, and end-to-end approaches: How to test UIs with Storybook.
1. Create a story that represents a useful state
A story is a reproducible fixture: it supplies props, context, and other setup for one component state. Give stories meaningful states such as empty, loading, validation error, and success. Keep external dependencies predictable by supplying stable mock data and mocked actions where needed.
// SaveButton.stories.ts
import type { Meta, StoryObj } from '@storybook/react-vite';
import { fn } from 'storybook/test';
import { SaveButton } from './SaveButton';
const meta = {
component: SaveButton,
args: {
onSave: fn(),
},
} satisfies Meta<typeof SaveButton>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Idle: Story = {
args: { label: 'Save', disabled: false },
};
export const Disabled: Story = {
args: { label: 'Save', disabled: true },
};
This TypeScript example uses the React Vite framework package. Change the framework import to match your project. The example assumes a component accepting label, disabled, and onSave props; adapt those names to the component being documented.
2. Run render and component checks
Every story can be render-tested: this checks that it can mount in its configured state. It is a useful broad smoke check, especially for a design system with many stories, but a successful render says little about what happens after a user acts.
With the Vitest addon, Storybook transforms stories into tests and runs them in browser mode. It can run from the Storybook UI, editor, command line, or CI, depending on your setup. The addon does not require a running Storybook instance. Its framework compatibility is narrower than the test-runner’s documented framework coverage, so confirm the current addon requirements first.
To add the addon using Storybook’s setup command, follow the current docs for your installed version. The docs show:
npx storybook add @storybook/addon-vitest
Do not assume that command or its generated configuration is identical across Storybook versions. Check the resulting config and the addon’s compatibility guide before adding CI commands.
3. Test behavior with a story play function
A play function runs after the story renders. Query elements by accessible role and name, simulate user input, then assert on an observable result. This keeps setup and behavior next to the story that defines the initial state. Storybook’s interaction testing guide describes this pattern and the Interactions panel used to inspect steps.
// LoginForm.stories.ts
import type { Meta, StoryObj } from '@storybook/react-vite';
import { expect, userEvent, within } from 'storybook/test';
import { LoginForm } from './LoginForm';
const meta = { component: LoginForm } satisfies Meta<typeof LoginForm>;
export default meta;
type Story = StoryObj<typeof meta>;
export const RejectsInvalidEmail: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.type(canvas.getByRole('textbox', { name: /email/i }), 'not-an-email');
await userEvent.click(canvas.getByRole('button', { name: /sign in/i }));
await expect(canvas.getByText(/enter a valid email/i)).toBeVisible();
},
};
The component in this example is expected to expose an accessible textbox named “Email,” a “Sign in” button, and an error message. Adjust labels and expected results to your app. Prefer visible outcomes or calls to injected mocked actions over assertions about internal implementation details.
- Open the story in Storybook and run its interaction test.
- Use the Interactions panel to inspect, pause, resume, or debug the recorded steps.
- Automate the story tests with the runner that matches your framework and version.
- Keep interaction tests focused on behaviors whose failure matters. Applying detailed interaction scripts to every story can create maintenance work; combine them with cheaper checks and visual review.
4. Add automated accessibility checks
Storybook’s Accessibility addon uses axe-core to audit rendered markup. It reports violations, passes, and incomplete checks. Incomplete results need human review because the tool could not decide automatically. Storybook’s documentation says axe-core automatically catches “up to 57% of WCAG issues”; treat this as the documented upper bound of automated detection, not as a coverage guarantee or proof of conformance.
Install and configure the addon with the command documented by Storybook:
npx storybook add @storybook/addon-a11y
Then inspect each story in the Accessibility panel. Configure checks through parameters.a11y at project, component, or story scope. The context setting controls which elements are checked; config configures axe rules; options configures the axe run; and test determines behavior when checks run with supported test integrations. See the accessibility testing docs for current options and defaults.
// .storybook/preview.ts
import type { Preview } from '@storybook/react-vite';
const preview: Preview = {
parameters: {
a11y: {
test: 'error',
},
},
};
export default preview;
Use the failure behavior deliberately: setting the test behavior to error makes detected violations fail automated runs where that configuration is supported. Do not assume every project fails CI on every finding by default. Review your runner’s configuration and decide how to handle violations and incomplete results.
Automated checks do not replace keyboard testing, screen reader review, or design judgment. A clean scan only means the automated rules did not report a violation for the tested render and configuration.
5. Catch appearance changes with visual tests
Visual tests compare rendered story images with known-good baselines. When a diff appears, review whether it is an intended design change, an environment difference, or a regression before updating the baseline. Storybook identifies Chromatic as a cloud option for cross-browser visual testing in its testing overview.
Keep visual tests for representative states where appearance matters: for example, button variants, modal layouts, responsive navigation, and error states. Ensure fonts, data, viewport, and animation state are stable enough for meaningful comparisons. A visual match does not verify that controls work or that markup is accessible.
6. Know when snapshots, unit tests, and end-to-end tests fit
Markup snapshots compare rendered markup with a stored representation. They are different from visual snapshots, which compare rendered pixels. Storybook presents markup snapshots as useful in selected cases, such as noticing markup changes associated with rendering errors or warnings; other testing methods may provide more coverage for less effort.
You can import stories into unit-test environments such as Vitest or Jest, or use stories within Playwright or Cypress end-to-end tests. Choose end-to-end coverage when the behavior depends on application routing, network integration, authentication, or other parts of the running app. Keep component-level tests for focused state and interaction feedback.
7. Choose a runner and automate it in CI
Pick the execution tool after listing the tests you need and checking framework support. Storybook’s current documentation compares the Vitest addon and test-runner as follows; details can change, so verify against your Storybook release:
| Decision | Vitest addon | Test-runner |
|---|---|---|
| Frameworks | Vite-based Storybook frameworks, with documented framework-specific routes | Documented for broader framework compatibility |
| Storybook server needed | No running Storybook instance required for its test integration | Requires a running or published Storybook |
| Test modes in the documented comparison | Interaction, accessibility, visual | Interaction, accessibility, markup snapshot |
| Where it runs | Storybook UI and editor integrations, plus CLI/CI workflows | CLI-oriented |
| Maintenance status | Check current compatibility and support docs | Official support has ended; investigate migration or project-specific constraints |
For Vite projects, the current docs favor checking Vitest addon compatibility. The test-runner listing explicitly says official support has ended and points Vite-based projects toward the Vitest integration. For projects that cannot use the addon, verify maintained choices for your exact framework rather than assuming the legacy runner is a supported new default.
For CI, use this sequence:
- Pin compatible Storybook, framework, and test integration versions using your normal dependency policy.
- Use the setup and run commands from the docs matching those installed versions.
- Make CI build or serve Storybook only if your chosen runner requires it.
- Set failure rules explicitly for interaction assertions and accessibility violations.
- Review visual diffs and update baselines only after confirming the change is intentional.
- Keep logs and the failing story or test output available so a CI failure is diagnosable.
8. A practical layered strategy
- For every important state: keep the story deterministic and make sure it renders.
- For critical user flows: add focused play-function tests for inputs, actions, and visible outcomes.
- Across the component library: run automated accessibility checks and manually inspect incomplete results and representative keyboard/screen reader behavior.
- For appearance-sensitive UI: compare visual baselines and review diffs.
- At application boundaries: reuse selected stories in end-to-end tests for workflows that depend on the full app.
This distributes effort by risk: broad checks catch basic failures, targeted interaction tests cover important behavior, and visual or end-to-end checks cover the risks those component tests cannot see.
Or skip the browser setup
If you need a screenshot of a Storybook page or a deployed story, ScreenshotNeo can capture a URL as PNG, JPEG, WebP, or PDF. It is a screenshot API and MCP server; it does not replace Storybook’s interaction, accessibility, or baseline comparison tests. The API has options for viewport, device presets, full-page capture, element capture, waiting, custom CSS, and other capture settings. See the ScreenshotNeo API docs.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://storybook.example.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://storybook.example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://storybook.example.com',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));
Replace the example URL with a public Storybook URL that does not require an interactive login. Cookie banners, popups, and chat widgets are removed before capture; each step can be turned off. Bot checks, blank pages, and failed loads are never billed. The MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Responses include page-verdict and billing headers; cache hits also cost nothing. Every feature is on every plan. Sign up for 1,000 free screenshots a month, with no card.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| A story fails before its play function runs | The component cannot render with the story’s args, decorators, providers, or fixtures | Open the story directly, inspect the render error, and correct missing context or invalid fixture data first. |
| A role or label query cannot find an element | The accessible name differs, the element is not rendered yet, or the story uses an inaccessible control | Inspect the rendered story and query by the actual role and accessible name. Fix the component’s semantics where appropriate. |
| An interaction test is flaky | Uncontrolled async work, timers, animations, network data, or unstable selectors | Use deterministic fixtures and mocked dependencies, wait for observable outcomes, and disable or stabilize animation for tests where it interferes. |
| Accessibility results show “incomplete” | The rule needs human judgment or context that automation cannot determine | Inspect the highlighted element manually; do not count incomplete as a pass. |
| CI reports accessibility findings differently than expected | parameters.a11y.test or runner configuration differs from the assumed behavior |
Set and verify the intended test behavior for the installed addon and runner, then confirm it locally and in CI. |
| Visual tests produce noisy diffs | Fonts, viewport, animation, dynamic content, or browser environment changed | Stabilize those inputs and review whether the difference is intentional before accepting a new baseline. |
| The Vitest addon does not load or support the project | The framework, builder, or Storybook version is outside its current compatibility range | Check the current compatibility docs for the exact versions; choose a currently supported integration for the project. |
| The test-runner setup breaks after an upgrade | Its official support has ended and compatibility may not track current Storybook releases | Consult the migration guide and evaluate the Vitest addon if the project is compatible. |
Performance, reliability, and cost
Rendering and accessibility checks generally cover more stories with less authored behavior than a large suite of detailed interactions. Interaction tests add useful confidence but can cost more to maintain when copied across every component. Visual suites require time to review diffs and keep baselines meaningful. End-to-end coverage is appropriate for integration risk, but reserve it for workflows that need the full application.
For reliable results, keep story data stable, mock external actions, avoid dependence on production services, and make timing explicit. Run checks in an environment compatible with the selected runner. Treat a passing run as evidence about the tested stories, states, assertions, and environment—not as proof about untested states.
Storybook’s docs identify Chromatic as a hosted visual testing option, but the testing modes themselves do not imply a particular price. Check the current terms and pricing of any hosted service before adopting it. With ScreenshotNeo, only clean shots are billed: bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Its plans are Free (1,000 shots/month), Starter ($5 for 3,000), Growth ($15 for 15,000), Pro ($39 for 60,000), Scale ($99 for 250,000), and Business ($249 for 1,000,000); yearly billing gives two months free. ScreenshotNeo captures pages; it is not a visual-regression review or test runner.
FAQ
Does every story need a play function?
No. Use play functions where a meaningful user flow needs assertions. Simple states may only need to render, accessibility-check, or visually compare.
Does a passing accessibility scan mean a story is accessible?
No. Automated rules detect only some issues, and incomplete checks require manual review.
Are visual snapshots and markup snapshots the same?
No. Visual snapshots compare rendered images; markup snapshots compare rendered structure.
Can I use stories in Playwright or Cypress?
Yes. Storybook documents reusing stories in end-to-end tests; use that when you need to exercise a larger application workflow.
Should a new project use the Storybook test-runner?
Check current guidance before choosing it: official support has ended. For Vite-based projects, first verify whether the Vitest addon supports the project’s framework and versions.


