Use when adding interaction testing to Storybook stories. Enables automated testing of component behavior, user interactions, and state changes directly in stories.
View on GitHubJanuary 24, 2026
Select agents to install to:
npx add-skill https://github.com/TheBushidoCollective/han/blob/main/jutsu/jutsu-storybook/skills/storybook-play-functions/SKILL.md -a claude-code --skill storybook-play-functionsInstallation paths:
.claude/skills/storybook-play-functions/# Storybook - Play Functions
Write automated interaction tests within stories using play functions to verify component behavior, simulate user actions, and test edge cases.
## Key Concepts
### Play Functions
Play functions run after a story renders, allowing you to simulate user interactions:
```typescript
import { within, userEvent, expect } from '@storybook/test';
import type { Meta, StoryObj } from '@storybook/react';
import { LoginForm } from './LoginForm';
const meta = {
component: LoginForm,
} satisfies Meta<typeof LoginForm>;
export default meta;
type Story = StoryObj<typeof meta>;
export const FilledForm: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.type(canvas.getByLabelText('Email'), 'user@example.com');
await userEvent.type(canvas.getByLabelText('Password'), 'password123');
await userEvent.click(canvas.getByRole('button', { name: /submit/i }));
await expect(canvas.getByText('Welcome!')).toBeInTheDocument();
},
};
```
### Testing Library Integration
Storybook integrates with Testing Library for queries and interactions:
- `within(canvasElement)` - Scopes queries to the story
- `userEvent` - Simulates realistic user interactions
- `expect` - Jest-compatible assertions
- `waitFor` - Waits for async changes
### Test Execution
Play functions execute:
- When viewing a story in Storybook
- During visual regression testing
- In test runners for automated testing
- On story hot-reload during development
## Best Practices
### 1. Use Testing Library Queries
Use semantic queries to find elements:
```typescript
export const SearchFlow: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Good - Semantic queries
const searchInput = canvas.getByRole('searchbox');
const submitButton = canvas.getByRole('button', { name: /search/i });
const results = canvas.getByRole('list', { name: /results/i });
await userEve