Storybook Test
Storybook Test: component testing via play functions, interaction testing, accessibility checks, visual regression, test-runner, portable stories
You are an expert in using Storybook's testing capabilities for component-level interaction testing, accessibility audits, and visual regression. ## Key Points - Write play functions for every interactive story so the test runner catches regressions automatically; stories without play functions still verify the component renders without crashing. - Use `step()` inside play functions to group related interactions, making the Interactions panel readable and failures easier to locate. - Leverage `composeStories` to reuse story definitions in unit tests so you maintain a single source of truth for component states and avoid duplicating setup logic.
skilldb get testing-services-skills/Storybook TestFull skill: 202 linesStorybook Test — Testing
You are an expert in using Storybook's testing capabilities for component-level interaction testing, accessibility audits, and visual regression.
Core Philosophy
Overview
Storybook Test turns your stories into executable tests. Each story already renders a component in isolation with specific props and state; play functions add user interactions and assertions on top. The @storybook/test package re-exports Testing Library and Vitest utilities (expect, fn, userEvent) so you write interactions directly inside stories. The @storybook/test-runner then executes every story in a real browser via Playwright, catching rendering errors, interaction failures, and (optionally) accessibility violations automatically. This means your component documentation and your tests are the same artifact.
Setup & Configuration
Installation
# Core testing utilities (included in Storybook 8+)
npm install -D @storybook/test
# Test runner for CI execution
npm install -D @storybook/test-runner
# Accessibility addon for a11y checks
npm install -D @storybook/addon-a11y
Storybook config (.storybook/main.ts)
import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(ts|tsx)"],
addons: [
"@storybook/addon-essentials",
"@storybook/addon-a11y",
"@storybook/addon-interactions",
],
framework: "@storybook/react-vite",
};
export default config;
Test runner config (test-runner.ts)
import type { TestRunnerConfig } from "@storybook/test-runner";
import { checkA11yRules } from "@storybook/addon-a11y/test-utils";
const config: TestRunnerConfig = {
async postVisit(page, context) {
// Run axe accessibility checks on every story
await checkA11yRules(page, context);
},
};
export default config;
package.json scripts
{
"scripts": {
"storybook": "storybook dev -p 6006",
"test-storybook": "test-storybook --url http://127.0.0.1:6006"
}
}
Core Patterns
Interaction testing with play functions
// src/components/LoginForm.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { expect, fn, userEvent, within } from "@storybook/test";
import { LoginForm } from "./LoginForm";
const meta: Meta<typeof LoginForm> = {
component: LoginForm,
args: {
onSubmit: fn(),
},
};
export default meta;
type Story = StoryObj<typeof LoginForm>;
export const FilledAndSubmitted: Story = {
play: async ({ args, canvasElement, step }) => {
const canvas = within(canvasElement);
await step("Fill in credentials", async () => {
await userEvent.type(canvas.getByLabelText("Email"), "user@test.com");
await userEvent.type(canvas.getByLabelText("Password"), "secret123");
});
await step("Submit the form", async () => {
await userEvent.click(canvas.getByRole("button", { name: "Log in" }));
});
await expect(args.onSubmit).toHaveBeenCalledWith({
email: "user@test.com",
password: "secret123",
});
},
};
export const ValidationError: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Submit without filling fields
await userEvent.click(canvas.getByRole("button", { name: "Log in" }));
await expect(canvas.getByText("Email is required")).toBeInTheDocument();
},
};
Portable stories (using stories in Vitest/Jest)
// src/components/LoginForm.test.tsx
import { composeStories } from "@storybook/react";
import { render, screen } from "@testing-library/react";
import * as stories from "./LoginForm.stories";
const { FilledAndSubmitted, ValidationError } = composeStories(stories);
test("submits credentials", async () => {
const { container } = render(<FilledAndSubmitted />);
await FilledAndSubmitted.play({ canvasElement: container });
// assertions already ran inside the play function
});
test("shows validation errors", async () => {
const { container } = render(<ValidationError />);
await ValidationError.play({ canvasElement: container });
expect(screen.getByText("Email is required")).toBeInTheDocument();
});
Mock modules and network requests
// src/components/UserProfile.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { expect, within } from "@storybook/test";
import { UserProfile } from "./UserProfile";
const meta: Meta<typeof UserProfile> = {
component: UserProfile,
// Use Storybook loaders to fetch/mock data before render
loaders: [
async () => ({
user: { id: "1", name: "Alice", role: "admin" },
}),
],
render: (args, { loaded: { user } }) => <UserProfile user={user} {...args} />,
};
export default meta;
type Story = StoryObj<typeof UserProfile>;
export const AdminUser: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(canvas.getByText("Alice")).toBeInTheDocument();
await expect(canvas.getByText("admin")).toBeInTheDocument();
},
};
Best Practices
- Write play functions for every interactive story so the test runner catches regressions automatically; stories without play functions still verify the component renders without crashing.
- Use
step()inside play functions to group related interactions, making the Interactions panel readable and failures easier to locate. - Leverage
composeStoriesto reuse story definitions in unit tests so you maintain a single source of truth for component states and avoid duplicating setup logic.
Common Pitfalls
- Running
test-storybookagainst a Storybook that is not already serving; the test runner requires a live Storybook instance (start it first or use--urlwith a CI workflow that builds a static Storybook). - Forgetting to await interactions and assertions inside play functions; every
userEventandexpectcall is async, and skippingawaitcauses assertions to run before the interaction completes, producing false passes or flaky failures.
Anti-Patterns
Using the service without understanding its pricing model. Cloud services bill differently — per request, per GB, per seat. Deploying without modeling expected costs leads to surprise invoices.
Hardcoding configuration instead of using environment variables. API keys, endpoints, and feature flags change between environments. Hardcoded values break deployments and leak secrets.
Ignoring the service's rate limits and quotas. Every external API has throughput limits. Failing to implement backoff, queuing, or caching results in dropped requests under load.
Treating the service as always available. External services go down. Without circuit breakers, fallbacks, or graceful degradation, a third-party outage becomes your outage.
Coupling your architecture to a single provider's API. Building directly against provider-specific interfaces makes migration painful. Wrap external services in thin adapter layers.
Install this skill directly: skilldb add testing-services-skills
Related Skills
Cypress
"Cypress: end-to-end/component testing, commands, intercept (network mocking), fixtures, custom commands, CI, retries"
K6
k6: load testing, performance testing, stress testing, soak testing, thresholds, checks, scenarios, browser testing, CI integration
MSW
"MSW (Mock Service Worker): API mocking, request handlers, rest/graphql, browser/node, setupServer, network-level interception"
Pact
Pact: consumer-driven contract testing, provider verification, pact broker, API compatibility, microservice integration testing
Playwright
"Playwright: end-to-end testing, browser automation, selectors, assertions, fixtures, page objects, visual regression, CI, codegen"
Testing Library
"Testing Library: React/DOM testing, queries (getBy/findBy/queryBy), user events, screen, waitFor, render, accessibility-first"