Back to list
paulbreuler

storybook-testing

by paulbreuler

See the truth about your APIs runi is an open-source API client that verifies what AI generates. When 46% of developers don't trust AI output (Stack Overflow 2025 Developer Survey), you need a second opinion. runi is that opinion.

7🍴 0📅 Jan 24, 2026

SKILL.md


name: storybook-testing version: 1.0.0 description: Create and test Storybook stories with play functions, accessibility verification, and visual regression. Use this skill when creating, updating, or testing Storybook stories for runi components. Ensures consistent testing patterns, keyboard navigation, focus management, WCAG 2.1 AA compliance, and proper use of story templates and testing utilities.

Storybook Testing

This skill guides creation of Storybook stories with consistent testing patterns, play functions, and accessibility verification.

When to Use

  • Creating new Storybook stories for components
  • Adding play functions to test interactions
  • Testing keyboard navigation and accessibility
  • Creating visual regression test stories
  • Ensuring WCAG 2.1 AA compliance in stories

Controls-First Approach

CRITICAL: Use Storybook 10 controls for state variations instead of creating separate stories for every prop combination.

The Problem

We consolidated from 64+ story files and 500+ story exports down to 15-20 files and 50-75 stories (85% reduction). The root cause: creating separate stories for every prop combination instead of using controls.

The Solution

One Playground story per component with controls covers most cases.

Bad (Anti-Pattern - Before Consolidation):

export const Default: Story = { args: { variant: 'default' } };
export const Destructive: Story = { args: { variant: 'destructive' } };
export const Outline: Story = { args: { variant: 'outline' } };
export const Disabled: Story = { args: { disabled: true } };
export const Small: Story = { args: { size: 'sm' } };
export const Large: Story = { args: { size: 'lg' } };

Good (Correct Pattern - After Consolidation):

export const Playground: Story = {
  args: { variant: 'default', size: 'default', disabled: false },
  argTypes: {
    variant: { control: 'select', options: ['default', 'destructive', 'outline'] },
    size: { control: 'select', options: ['sm', 'default', 'lg'] },
    disabled: { control: 'boolean' },
  },
  play: async ({ canvasElement, step }) => {
    // Test interactions that work across all state variations
  },
};

Key Principles

  1. Use controls for state variations - Don't create separate stories for every prop combination
  2. One Playground story per component - Use controls to explore all features
  3. Limit to 1-3 stories per component - Most components need only one Playground story with controls
  4. Test via play functions - One comprehensive play function can test interactions across all state variations

When to Create Separate Stories

Create separate stories ONLY for:

  1. Complex interactions that need dedicated play functions (e.g., keyboard navigation tests that require specific setup)
  2. Real-world examples (e.g., NetworkHistoryPanel with real network data vs generic DataGrid)
  3. Documentation purposes (named examples like "Empty State" or "Loading State", but still use controls within those stories)

Rule of thumb: If you're creating a story just to show a different prop value, use controls instead.

Story Templates

Templates are located in .storybook/templates/ and provide reusable patterns for common story types.

Interaction Story Template

File: .storybook/templates/interaction-story.template.tsx

Use When:

  • Testing keyboard navigation (Tab, Enter, Space, Arrow keys)
  • Testing click interactions and state changes
  • Testing form input and submission
  • Testing user flows and workflows

Key Patterns:

  • Use tabToElement() from @/utils/storybook-test-helpers for keyboard navigation
  • Use step() to organize test actions into logical groups
  • Test one interaction concept per story

Example:

import { expect, userEvent, within } from 'storybook/test';
import { tabToElement } from '@/utils/storybook-test-helpers';

export const KeyboardNavigationTest: Story = {
  play: async ({ canvasElement, step }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByTestId('submit-button');

    await step('Tab to button', async () => {
      const focused = await tabToElement(button, 5);
      expect(focused).toBe(true);
      await expect(button).toHaveFocus();
    });
  },
};

Accessibility Story Template

File: .storybook/templates/accessibility-story.template.tsx

Use When:

  • Testing ARIA attributes and roles
  • Testing keyboard navigation and focus management
  • Testing screen reader compatibility
  • Ensuring WCAG 2.1 AA compliance

Key Patterns:

  • Enable a11y addon in story parameters for automatic checks
  • Use waitForFocus() for focus management tests
  • Test keyboard navigation manually (a11y addon doesn't test this)
  • Verify all interactive elements have accessible names

Example:

import { waitForFocus } from '@/utils/storybook-test-helpers';

export const FocusManagementTest: Story = {
  parameters: {
    a11y: {
      config: {
        rules: [{ id: 'color-contrast', enabled: true }],
      },
    },
  },
  play: async ({ canvasElement, step }) => {
    const canvas = within(canvasElement);

    await step('Focus moves to first element', async () => {
      const firstElement = canvas.getByTestId('first-focusable');
      await waitForFocus(firstElement, 1000);
      await expect(firstElement).toHaveFocus();
    });
  },
};

Visual Story Template

File: .storybook/templates/visual-story.template.tsx

Use When:

  • Testing visual regression (screenshots)
  • Testing responsive layout on different viewports
  • Testing theme variations (light/dark)
  • Testing loading, error, and empty states
  • Testing animations and transitions

Key Patterns:

  • Configure viewport in story parameters
  • Test both light and dark themes
  • Use consistent viewport sizes for visual regression
  • Use Playwright for automated visual regression (see tests/visual/)

Example:

export const LoadingState: Story = {
  render: (args) => <ComponentName {...args} isLoading={true} />,
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const loadingIndicator = canvas.getByTestId('loading-indicator');
    await expect(loadingIndicator).toBeVisible();
  },
};

Testing Utilities

Use utilities from @/utils/storybook-test-helpers for common testing patterns.

tabToElement(target, maxTabs?)

Tabs through focusable elements until the target receives focus.

const button = canvas.getByTestId('submit-button');
const focused = await tabToElement(button, 5);
expect(focused).toBe(true);

waitForFocus(element, timeout?)

Waits for an element to receive focus. Default timeout: 5000ms.

await waitForFocus(button, 1000);
await expect(button).toHaveFocus();

waitForRemount(selector, timeout?)

Waits for an element to be removed and re-added to the DOM. Default timeout: 5000ms.

await waitForRemount('[data-test-id="form"]', 2000);

waitForState(getState, expected, timeout?)

Waits for a state value to match an expected value. Default timeout: 5000ms.

let count = 0;
const getCount = () => count;
setTimeout(() => {
  count = 5;
}, 100);
await waitForState(getCount, 5, 1000);

Story Naming Conventions

Follow these naming patterns:

  • Default - Basic component with default props
  • WithContent - Component with realistic content
  • [StateName] - Specific state (e.g., LoadingState, ErrorState)
  • FullIntegration - Component with real child components
  • [Feature]Test - Stories with play functions for automated testing

Best Practices

Play Functions

  • Use step() to organize test actions into logical groups
  • Keep steps focused on a single interaction or verification
  • Use descriptive step names that explain what is being tested
  • Use testing utilities for common patterns (tab navigation, focus waiting)

Test IDs

  • Always use data-test-id attributes for test selectors
  • Make test IDs descriptive and stable (not dependent on text content)
  • Use getByTestId() instead of getByText() or getByRole() for component identification
  • Exception: Use semantic queries (getByRole, getByLabel) only when testing accessibility, not for component identification

Accessibility

  • Enable the a11y addon in story parameters for automatic checks
  • Test keyboard navigation manually (a11y addon doesn't test this)
  • Verify focus indicators are visible (use waitForFocus() to verify)
  • Test with screen readers when possible
  • Ensure all interactive elements have accessible names (aria-label, aria-labelledby, or text content)

Visual Testing

  • Use consistent viewport sizes for visual regression
  • Test both light and dark themes
  • Test responsive breakpoints (mobile, tablet, desktop)
  • Use Playwright for automated visual regression (see tests/visual/storybook-visual.spec.ts)

Story Structure

  • Keep stories minimal and focused (1 concept per story)
  • Add brief JSDoc comments explaining each story's purpose
  • Use storybook/test utilities (expect, userEvent, within) for assertions
  • Don't duplicate unit test coverage in stories
  • Limit to 1-3 stories per component - Use controls for state variations instead of separate stories
  • One Playground story with controls covers most cases

Example Stories

See existing stories for reference:

  • src/components/Layout/DockablePanel.stories.tsx - Interaction and focus testing
  • src/components/PanelTabs.stories.tsx - Tab navigation testing
  • src/components/History/ResponseTab.stories.tsx - Visual state testing

Testing Approaches

  1. Play Functions - For component interactions (recommended for most cases)
  2. Playwright E2E - For complex multi-component flows (see tests/e2e/)
  3. Vitest Addon - Convert stories to test cases automatically
  4. Accessibility Addon - Automatic a11y checks on all stories
  • CLAUDE.md - Storybook best practices section
  • docs/STORYBOOK_10_FEATURES.md - Storybook 10 features
  • src/utils/storybook-test-helpers.ts - Testing utility source code
  • .storybook/templates/ - Story template files

Score

Total Score

75/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon