Interactive stories (beta)
Simulate user behaviour using play functions
Building components in isolation allows you to stress test them to find edge cases. With Storybook, you write stories for each of these cases by supplying props to your component.
But not all component variations can be reproduced with props alone. Some UI states can only be reached via user interaction like dropdowns, modals, and hidden form elements. In the past, you interacted with the component by hand to check whether these states looked right.
I’m excited to announce interactive stories (beta), which allows you to simulate user behavior to run after the story renders. It cuts out the grunt work of messing with components manually.
- ✅ Runs in a real browser
- ⚡️ No waiting and no-flake
- 🐙 Powered by Testing Library
- 🛠 Low maintenance
- 🔍 Fast visual debugging
Play function for interactions
Stories isolate and capture component states in a structured manner. While developing a component, you can quickly cycle through the stories to verify the look and feel.
Each story specifies all the inputs required to reproduce a specific state. You can even mock context and API calls. That allows you to handle most use cases of a component.
But what about states that require user interaction?
For example, clicking a button to open/close a dialog box, dragging a list item to reorder it or filling out a form to check for validation errors. To test those behaviours, you have to interact with the components as a user would.
Interactive stories enable you to automate these interactions using a play function. These are small snippets of code that script out the exact steps a human would take to interact with the component. It’s then executed as soon as the story is rendered.
Powered by Testing Library
The interactions are written using a Storybook-instrumented version of Testing Library. That gives you a familiar developer-friendly syntax to interact with the DOM, but with extra telemetry to help with debugging. Here’s a basic example that demonstrates clicking a button to open a dialog box.
import React from 'react';
import { within, fireEvent } from '@storybook/testing-library';
import { DeleteCustomerDialog } from './DeleteCustomerDialog';
export default {
component: DeleteCustomerDialog,
title: 'DeleteCustomerDialog',
};
export const OpenDialog = () => <DeleteCustomerDialog />;
OpenDialog.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
await fireEvent.click(
canvas.getByRole('button', { name: 'Delete Customer' })
);
};
If you can render it in Storybook, then you can write an interactive story for it.
Interactive stories use framework-agnostic DOM APIs. That means, regardless of which framework you use, you can write a play function to manipulate the UI and automate user behaviour. The only caveat is Web Components because Testing-Library doesn’t support shadow DOM yet.
Debug faster with a GUI
Most teams already use Testing Library to write functional tests for their components. These tests are executed using a test runner like Jest. They run in Node using JSDOM. If a test fails, all you get is a blob of HTML to debug.
Debugging UI issues with a CLI is like playing a text adventure game on your Playstation 5. It works, but it would be so much better if you could see some graphics.
Interactive stories use a Storybook-specific version of Testing Library. It runs in the browser and visualizes the entire play function for you. Think of it as the GUI for Testing Library.
If a story fails, the interactions panel highlights the broken step. You can then debug the UI right in the browser with all your favourite dev tools. Not in JSDOM using opaque CLIs. Being able to see and inspect the UI makes debugging a breeze.
Storybook linter
Interactive stories and the interactions panel are both enabled by the instrumented version of Testing Library. That means you have to use helper functions from @storybook/testing-library
and ensure that your stories are written using the proper syntax. We're working on an ESLint plugin to enforce these checks automatically. It'll be released along with Storybook 6.4. Stay tuned!
Let’s look at the entire workflow with a demo.
Interactive stories example
Consider the Taskbox app — a task management app similar to Asana. It displays a list of tasks that the user can pin, archive and edit.
Here’s what the story looks like:
import React from 'react';
import { rest } from 'msw';
import { within, fireEvent, findByRole } from '@storybook/testing-library';
import { InboxScreen } from './InboxScreen';
import { Default as TaskListDefault } from './components/TaskList.stories';
export default {
component: InboxScreen,
title: 'InboxScreen',
};
const Template = (args) => <InboxScreen {...args} />;
export const Default = Template.bind({});
Default.parameters = {
msw: [
rest.get('/tasks', (req, res, ctx) => {
return res(ctx.json(TaskListDefault.args));
}),
],
};
export const UpdateTasks = Template.bind({});
UpdateTasks.parameters = { ...Default.parameters };
UpdateTasks.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const getTask = (name) => canvas.findByRole('listitem', { name });
// Pin
const itemToPin = await getTask('Export logo');
const pinButton = await findByRole(itemToPin, 'button', { name: 'pin' });
await fireEvent.click(pinButton);
// Archive
const itemToArchive = await getTask('QA dropdown');
const archiveCheckbox = await findByRole(itemToArchive, 'checkbox');
await fireEvent.click(archiveCheckbox);
// Edit
const itemToEdit = await getTask('Fix bug in input error state');
const taskInput = await findByRole(itemToEdit, 'textbox');
await fireEvent.change(taskInput, {
target: { value: 'Fix bug in the textarea error state' },
});
// Delete
const itemToDelete = await getTask('Build a date picker');
const deleteButton = await findByRole(itemToDelete, 'button', {
name: 'delete',
});
await fireEvent.click(deleteButton);
};
The story uses the MSW addon to mock /tasks
API call. That mock data is used to render the task list and the interactions live inside the play function.
Try it today and share your feedback
Interactive stories are now available in the Storybook 6.4 beta. This experimental release lays the foundation for the broader interaction testing feature we announced in August. Storybook 6.4 gives us a chance to test and stabilize this feature before a full release in Storybook 6.5. Give it a go and share your feedback with us.
Install it by running the following command at the root of your project.
npx sb@next init
To upgrade an existing project:
npx sb upgrade --prerelease
Then install interactive stories related dependencies:
yarn add -D @storybook/addon-interactions @storybook/testing-library
And enable the debugger within .storybook/main.js
. Note that @storybook/addon-interactions
must be listed after @storybook/addon-actions
or @storybook/addon-essentials
.
// .storybook/main.js
module.exports = {
addons: ['@storybook/addon-interactions'],
};
Get involved
Interactive stories make it easier to test functional aspects of a component. You script interactions via play functions and Storybook runs them automatically for you. The handy interactions panel visualizes every interaction in the play function.
The Interactive stories feature was developed by Gert Hengeveld, Deen Denno, Yann Braga, Michael Shilman, Tom Coleman, Michael Arestad, and Dominic Nguyen with feedback from the entire Storybook community.
If Storybook makes your UI development workflow easier, help Storybook get better. You can contribute a new feature, fix a bug, or improve the docs. Join us on Discord or just jump in on Github. To get project updates and early access to features, sign up to Storybook’s mailing list below.