React renderer for Storybook framework providing TypeScript support, portable stories, and React-specific functionality
—
Testing utilities and experimental Playwright integration for enhanced testing workflows. Provides tools for integrating Storybook stories with various testing frameworks and environments.
The testing integration features depend on Storybook preview API:
// Experimental Playwright integration
import { createTest } from "@storybook/react/experimental-playwright";
// Portable stories for testing (see Portable Stories documentation)
import { composeStory, composeStories, setProjectAnnotations } from "@storybook/react";
// Re-exported from storybook/preview-api
import { createPlaywrightTest } from "storybook/preview-api";Experimental Playwright testing utility for creating test functions that work with Storybook stories.
/**
* Creates Playwright test function for Storybook integration.
* Re-exported from storybook/preview-api for convenience.
*
* @returns Playwright test creation function
*/
function createTest(): any;This function is part of the experimental Playwright integration and provides utilities for running Storybook stories in Playwright test environments.
Usage Example:
import { createTest } from "@storybook/react/experimental-playwright";
const test = createTest();
test("Button stories", async ({ page }) => {
// Use with Playwright for visual testing
await page.goto("/storybook-iframe.html?id=example-button--primary");
await expect(page).toHaveScreenshot("primary-button.png");
});While not directly exported from @storybook/react, the package is designed to work seamlessly with React Testing Library through portable stories:
import { render, screen, fireEvent } from "@testing-library/react";
import { composeStory, composeStories } from "@storybook/react";
import Meta, { Primary, Secondary } from "./Button.stories";
// Single story testing
const PrimaryButton = composeStory(Primary, Meta);
test("renders primary button", () => {
render(<PrimaryButton />);
expect(screen.getByRole("button")).toHaveClass("primary");
});
// Testing with custom props
test("button accepts custom props", () => {
render(<PrimaryButton size="large" onClick={vi.fn()} />);
expect(screen.getByRole("button")).toHaveClass("large");
});
// Multiple story testing
const { Primary: PrimaryStory, Secondary: SecondaryStory } = composeStories(Meta);
describe("Button component", () => {
test("all stories render correctly", () => {
Object.entries(composeStories(Meta)).forEach(([name, Story]) => {
render(<Story />);
expect(screen.getByRole("button")).toBeInTheDocument();
});
});
});import { beforeAll } from "vitest";
import { setProjectAnnotations } from "@storybook/react";
import * as projectAnnotations from "../.storybook/preview";
// Setup global configuration
beforeAll(() => {
setProjectAnnotations(projectAnnotations);
});
// Test individual stories
import { composeStory } from "@storybook/react";
import Meta, { Default } from "./Component.stories";
const DefaultStory = composeStory(Default, Meta);
test("story renders with default args", () => {
render(<DefaultStory />);
// Test the rendered component
});import { render } from "@testing-library/react";
import { composeStory } from "@storybook/react";
import { ThemeProvider } from "./theme";
import Meta, { Themed } from "./Button.stories";
const ThemedButton = composeStory(Themed, Meta);
test("button works with theme context", () => {
render(
<ThemeProvider theme="dark">
<ThemedButton />
</ThemeProvider>
);
// Test themed behavior
});// Using @storybook/test-runner or Playwright
import type { TestRunnerConfig } from "@storybook/test-runner";
const config: TestRunnerConfig = {
async postRender(page, context) {
// Take screenshot of each story
const elementHandler = await page.$("#storybook-root");
const innerHTML = await elementHandler.innerHTML();
expect(innerHTML).toMatchSnapshot(`${context.id}.html`);
},
};
export default config;import { render } from "@testing-library/react";
import { axe, toHaveNoViolations } from "jest-axe";
import { composeStories } from "@storybook/react";
import * as stories from "./Button.stories";
expect.extend(toHaveNoViolations);
const { Primary, Secondary } = composeStories(stories);
test("Primary button should be accessible", async () => {
const { container } = render(<Primary />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});import { render, fireEvent } from "@testing-library/react";
import { composeStory } from "@storybook/react";
import { fn } from "@storybook/test";
import Meta, { Interactive } from "./Button.stories";
const InteractiveButton = composeStory(Interactive, Meta);
test("button calls onClick handler", () => {
const mockFn = fn();
render(<InteractiveButton onClick={mockFn} />);
fireEvent.click(screen.getByRole("button"));
expect(mockFn).toHaveBeenCalledTimes(1);
});// test-setup.ts
import "@testing-library/jest-dom";
import { setProjectAnnotations } from "@storybook/react";
import * as projectAnnotations from "../.storybook/preview";
setProjectAnnotations(projectAnnotations);// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "jsdom",
setupFiles: ["./test-setup.ts"],
},
});// jest.config.js
module.exports = {
testEnvironment: "jsdom",
setupFilesAfterEnv: ["<rootDir>/test-setup.ts"],
transform: {
"^.+\\.(ts|tsx)$": "ts-jest",
},
};import { composeStory } from "@storybook/react";
import Meta, { WithBackground } from "./Button.stories";
const ButtonWithBackground = composeStory(WithBackground, Meta);
test("story uses correct background parameter", () => {
expect(ButtonWithBackground.parameters.backgrounds.default).toBe("dark");
});const PrimaryButton = composeStory(Primary, Meta);
test("story has correct default args", () => {
expect(PrimaryButton.args).toEqual({
primary: true,
label: "Button",
size: "medium",
});
});import { composeStory } from "@storybook/react";
import Meta, { WithData } from "./DataComponent.stories";
const DataComponent = composeStory(WithData, Meta);
test("story with loader works correctly", async () => {
// The story's loader will run automatically when composed
render(<DataComponent />);
// Test the component with loaded data
await waitFor(() => {
expect(screen.getByText("Loaded Data")).toBeInTheDocument();
});
});The testing integration includes experimental features that may change in future versions:
These features are available through the experimental-playwright export and should be used with caution in production environments.
Install with Tessl CLI
npx tessl i tessl/npm-storybook--react