Modern QA2026Writing Stories for Visual Testing — tiles
Log inJoin
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
    },
};