59 / 74 · 10 Visual & Accessibility Testing · Component-Level Visual Testing in Storybook← prev⊞ allnext →☰ Read as one page
11.3Writing Stories for Visual Testing
DataTable Component
// DataTable.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { DataTable } from './DataTable';
import { within, userEvent } from '@storybook/testing-library';
const meta: Meta<typeof DataTable> = {
title: 'Components/DataTable',
component: DataTable,
parameters: {
chromatic: {
viewports: [375, 768, 1200],
},
},
};
export default meta;
export const WithData: StoryObj<typeof DataTable> = {
args: {
columns: [
{ key: 'name', header: 'Product Name', sortable: true },
{ key: 'price', header: 'Price', sortable: true },
{ key: 'stock', header: 'In Stock', sortable: false },
],
data: [
{ name: 'Widget A', price: '$29.99', stock: 'Yes' },
{ name: 'Widget B', price: '$49.99', stock: 'No' },
{ name: 'Widget C', price: '$19.99', stock: 'Yes' },
],
},
};
export const Empty: StoryObj<typeof DataTable> = {
args: {
columns: [{ key: 'name', header: 'Product Name' }],
data: [],
},
};
export const Loading: StoryObj<typeof DataTable> = {
args: {
columns: [{ key: 'name', header: 'Product Name' }],
data: [],
loading: true,
},
};
export const WithError: StoryObj<typeof DataTable> = {
args: {
columns: [{ key: 'name', header: 'Product Name' }],
data: [],
error: 'Failed to load product data. Please try again.',
},
};
// Interaction testing: verify sort behavior
export const SortedByPrice: StoryObj<typeof DataTable> = {
...WithData,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Click the Price column header to sort
await userEvent.click(canvas.getByText('Price'));
// Visual snapshot captures the sorted state
},
};
Testing All Component States
A visual regression suite for a single component should cover all meaningful states:
// Button.stories.tsx -- comprehensive state coverage
export const AllStates: StoryObj<typeof Button> = {
render: () => (
<div style={{ display: 'grid', gap: '1rem', maxWidth: '400px' }}>
<h3>Primary</h3>
<Button variant="primary">Default</Button>
<Button variant="primary" disabled>Disabled</Button>
<Button variant="primary" loading>Loading</Button>
<h3>Secondary</h3>
<Button variant="secondary">Default</Button>
<Button variant="secondary" disabled>Disabled</Button>
<h3>Danger</h3>
<Button variant="danger">Default</Button>
<Button variant="danger" disabled>Disabled</Button>
<h3>Sizes</h3>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
<h3>With Icons</h3>
<Button icon="plus">Add Item</Button>
<Button icon="trash" variant="danger">Delete</Button>
</div>
),
parameters: {
chromatic: {
viewports: [375, 1200],
},
},
};
// Focus state -- important for accessibility visual testing
export const FocusState: StoryObj<typeof Button> = {
args: { variant: 'primary', children: 'Focused Button' },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button');
await button.focus();
// Snapshot captures the focus ring
},
};