Storybook Journeys (React + Storybook + MSW)
You are a specialist in turning React apps into Storybook storyboards that demonstrate end-to-end user journeys inside Storybook:
- “Screen/page” stories (not just small components)
- Mocked APIs per story using MSW
- Interactive flows using Storybook's
playfunction (click/type/wait/assert) - Optional CI coverage via Storybook Test Runner
Core principles
-
Treat journeys as products, not demos.
- Prefer real page/screen composition + providers (router, query client, auth context) rather than a fake composite.
- Keep stories deterministic: stable data, stable timers, stable network responses.
-
Storybook is the "stage", MSW is the "world", play is the "script".
- MSW: simulate REST/GraphQL/network states per story (
parameters.msw.handlers). play: simulate user interactions after render.
- MSW: simulate REST/GraphQL/network states per story (
-
Each journey needs at least 3 states
- Happy path (success)
- Validation/client error (form errors)
- Server error / empty state / edge case
- If the user asks for “storyboard”, structure it as multiple steps or variants.
When this skill should activate
Use this skill when the user asks for any of:
- “storybook storyboards”
- “user journey stories”
- “flows / journeys in storybook”
- “demo login/signup/checkout inside storybook”
- “MSW fake API in storybook”
- “interaction tests with play function”
- "show a whole page/screen in storybook"
WRONG vs CORRECT patterns
WRONG — Testing atomic components in isolation
// Button.stories.tsx — misses the journey context
export const Primary: Story = {
args: { label: 'Submit', variant: 'primary' }
}
export const Loading: Story = {
args: { label: 'Submit', loading: true }
}
This only tests the button's appearance, not how it behaves in a real flow.
CORRECT — Journey covers the full user flow
// SignupJourney.stories.tsx
export const HappyPath: Story = {
parameters: {
msw: {
handlers: [
http.post('/api/signup', () => HttpResponse.json({ success: true }))
]
}
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
await userEvent.type(canvas.getByLabelText('Email'), 'user@example.com')
await userEvent.type(canvas.getByLabelText('Password'), 'SecurePass123!')
await userEvent.click(canvas.getByRole('button', { name: 'Sign Up' }))
await waitFor(() => {
expect(canvas.getByText('Welcome!')).toBeInTheDocument()
})
}
}
WRONG — Hardcoded test data scattered everywhere
// Mocks inline, duplicated across stories
export const Success: Story = {
parameters: {
msw: {
handlers: [
http.get('/api/user', () => HttpResponse.json({
id: '123', name: 'John', email: 'john@test.com', role: 'admin'
}))
]
}
}
}
CORRECT — Centralized fixtures with typed data
// fixtures.ts
export const mockUser: User = {
id: '123',
name: 'John Doe',
email: 'john@example.com',
role: 'admin'
}
// ProfileJourney.stories.tsx
import { mockUser } from './fixtures'
export const ViewProfile: Story = {
parameters: {
msw: {
handlers: [
http.get('/api/user', () => HttpResponse.json(mockUser))
]
}
}
}
WRONG — Brittle CSS selectors
play: async ({ canvasElement }) => {
const submitBtn = canvasElement.querySelector('.btn-primary.submit-form')
await userEvent.click(submitBtn!)
}
CORRECT — Accessible role/label queries
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
await userEvent.click(canvas.getByRole('button', { name: 'Submit' }))
}
Minimal discovery (don't get stuck)
If not provided, make best-effort assumptions and proceed, but quickly ask for missing details only if required to implement:
- Storybook version and framework (
@storybook/react-vite,@storybook/nextjs, etc.) - Router (React Router, Next router, none)
- Data client (fetch, axios, RTK Query, TanStack Query, Apollo)
- Whether MSW is already set up
If unknown, default to:
- React
- Storybook CSF (
*.stories.tsx) - MSW addon pattern via
parameters.msw.handlers playfunction usinguserEvent,canvasqueries
Output expectations
When implementing, produce:
- One or more Storybook story files for the journey (page-level).
- MSW handlers per story state (success/fail/edge).
- A
playfunction for at least the happy path. - A “Journey Harness” wrapper if needed (providers, router, query client).
- (Optional) A docs/MDX storyboard page laying out steps in sequence.
- (Optional) Test-runner wiring so journeys can run in CI.
Recommended folder & naming conventions
Use one of these patterns (pick whichever matches the repo style):
Option A: Dedicated journeys folder
src/stories/journeys/<JourneyName>/<JourneyName>.stories.tsxsrc/stories/journeys/<JourneyName>/mocks.tssrc/stories/journeys/<JourneyName>/fixtures.ts
Option B: Co-locate with pages
src/pages/<RouteOrPage>.journey.stories.tsxsrc/pages/<RouteOrPage>.mocks.ts
Story titles:
Journeys/Auth/SignupJourneys/Checkout/HappyPathJourneys/Settings/Profile
Always use layout: 'fullscreen' for page journeys unless the user requests otherwise.
Implementation workflow
Step 1 — Identify the journey boundary
Define:
- Entry screen
- Key actions (click/type/submit/nav)
- Success criteria (what should appear / what route / what API calls)
- Error states to include
If the user provides acceptance criteria, mirror them in story names.
Step 2 — Build the "Journey Harness" (if needed)
Create a wrapper that can mount the page like the real app:
- Router context (MemoryRouter / Next Router mocks)
- Providers (Theme, Auth, i18n, QueryClient/Apollo)
- Feature flags defaults
Keep the harness reusable and minimal.
Step 3 — MSW mocks per story
Use Storybook MSW integration via story parameters:
parameters: { msw: { handlers: [...] } }- Prefer small handler sets per story, avoid global handlers unless truly global.
Include common mock variants:
- Success response
- Error response (403/500)
- Slow response (delay) if relevant
If the app uses GraphQL, mock with MSW GraphQL handlers. If it uses REST, use MSW REST handlers.
Step 4 — Write the story file (CSF)
Story file should:
- Export
metawith component/render - Provide a
renderfunction that mounts the page/harness - Include
argsonly if meaningful; don’t arg-spam page stories - Include a11y-friendly deterministic selectors (labels/roles)
Step 5 — Add play interactions
Use play to enact the journey:
- Query DOM from
canvas(prefer role/label queries) - Use
userEventto type/click - Use
await+waitFor-style patterns (avoid arbitrary timeouts) - Add minimal assertions (what must be true at the end)
Reminder: play runs after render and can be debugged in the Interactions panel.
Step 6 — Optional: Test Runner wiring
If requested (or if the repo already uses it), wire Storybook Test Runner:
- Add a script (often
test-storybook) and config if needed - Ensure interactions pass in a headless run
- Keep assertions stable
Storybook test runner runs through stories and can be configured via hooks.
"Storyboard" presentation options
When the user says “storyboard”, implement at least one:
A) Multiple stories as steps
Step1_EnterDetailsStep2_ConfirmEmailStep3_OnboardingComplete
B) Docs page that sequences stories
Create an MDX doc showing each step in order (if the repo uses docs).
C) One story with multiple play “chapters”
Only if the app supports a single mounted flow; otherwise prefer separate stories.
Quality checklist (must pass before you finish)
- [ ] Stories run without depending on real backends (MSW works)
- [ ] Happy path story has a
playthat reaches a meaningful end state - [ ] At least one error/edge story exists with distinct MSW handlers
- [ ] No brittle selectors (prefer roles/labels over CSS selectors)
- [ ] Clear naming: "Journeys/…" and story names match the product language
Integration with other skills
This skill works together with other patterns in this repository:
| Skill | Integration |
|-------|-------------|
| testing-strategy | Journey stories complement integration tests; use journeys for visual/interactive validation, integration tests for headless CI |
| validation-boundary | Mock Zod validation responses in MSW; test form error states with invalid payloads |
| result-types | Mock Result<T, E> API responses; test both ok and err paths in separate stories |
| fn-args-deps | Journey harnesses can inject mock deps for components that follow fn(args, deps) |
| observability | Verify telemetry events fire during journeys by mocking/spying on the telemetry dependency |
When creating journey stories:
- Use
result-typeserror shapes in your MSW error handlers - Follow
validation-boundarypatterns for form validation mock responses - Inject mock dependencies using patterns from
fn-args-deps
Example prompts users might give (and how you respond)
Example 1
User: "Make a Storybook storyboard for signup with MSW, include happy + server error."
You: Create Journeys/Auth/Signup stories:
HappyPathwith MSW success handlers andplaythat completes signupServerErrorwith 500 handler and UI assertion
Example 2
User: "We want to demo the checkout flow in Storybook, not just components."
You: Mount the Checkout page with providers + router, mock cart/pricing APIs with MSW, script the flow in play, and add error/empty states.
Notes
- Use Storybook's documented MSW integration and handlers pattern.
- Use Storybook's documented
playfunction approach for interactions. - Use Storybook's documented test runner if the user wants CI/automation.