播放函数
Play 函数是故事渲染后执行的小代码片段。它们使你能够与组件进行交互并测试原本需要用户干预的场景。
¥Play functions are small snippets of code executed after the story renders. They enable you to interact with your components and test scenarios that otherwise require user intervention.
使用 play 函数编写故事
¥Writing stories with the play function
Storybook 的 play 函数是故事完成渲染后运行的小代码片段。在 交互面板 的帮助下,它允许你构建组件交互和测试场景,而这些在没有用户干预的情况下是不可能实现的。例如,如果你正在开发注册表单并想要验证它,你可以使用 play 函数编写以下故事:
¥Storybook's play functions are small code snippets that run once the story finishes rendering. Aided by the interactions panel, it allows you to build component interactions and test scenarios that were impossible without user intervention. For example, if you were working on a registration form and wanted to validate it, you could write the following story with the play function:
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { RegistrationForm } from './RegistrationForm';
const meta = {
component: RegistrationForm,
} satisfies Meta<typeof RegistrationForm>;
export default meta;
type Story = StoryObj<typeof meta>;
/*
* See https://storybook.js.org/docs/writing-stories/play-function#working-with-the-canvas
* to learn more about using the canvas to query the DOM
*/
export const FilledForm: Story = {
play: async ({ canvas, userEvent }) => {
const emailInput = canvas.getByLabelText('email', {
selector: 'input',
});
await userEvent.type(emailInput, 'example-email@email.com', {
delay: 100,
});
const passwordInput = canvas.getByLabelText('password', {
selector: 'input',
});
await userEvent.type(passwordInput, 'ExamplePassword', {
delay: 100,
});
const submitButton = canvas.getByRole('button');
await userEvent.click(submitButton);
},
};有关可用 API 事件的概述,请参阅 交互测试文档。
¥See the interaction testing documentation for an overview of the available API events.
当 Storybook 完成故事渲染时,它会执行 play 函数中定义的步骤,与组件交互并填写表单的信息。所有这些都无需用户干预。如果你检查 Interactions 面板,你将看到分步流程。
¥When Storybook finishes rendering the story, it executes the steps defined within the play function, interacting with the component and filling the form's information. All of this without the need for user intervention. If you check your Interactions panel, you'll see the step-by-step flow.
使用画布
¥Working with the canvas
传递给 play 函数的上下文部分是 canvas 对象。此对象允许你查询已渲染故事的 DOM。它提供了测试库查询的范围版本,因此你可以像在常规测试中一样使用它们。
¥Part of the context passed to the play function is a canvas object. This object allows you to query the DOM of the rendered story. It provides a scoped version of the Testing Library queries, so you can use them as you would in a regular test.
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { MyComponent } from './MyComponent';
const meta = {
component: MyComponent,
} satisfies Meta<typeof MyComponent>;
export default meta;
type Story = StoryObj<typeof meta>;
export const ExampleStory: Story = {
play: async ({ canvas, userEvent }) => {
// Starts querying from the component's root element
await userEvent.type(canvas.getByTestId('example-element'), 'something');
await userEvent.click(canvas.getByRole('button'));
},
};如果你需要在画布之外进行查询(例如,测试出现在故事根目录之外的对话框),你可以使用 storybook/test 提供的 screen 对象。
¥If you need to query outside of the canvas (for example, to test a dialog that appears outside of the story root), you can use the screen object available from storybook/test.
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { screen } from 'storybook/test';
import { Dialog } from './Dialog';
const meta = {
component: Dialog,
} satisfies Meta<typeof Dialog>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Open: Story = {
play: async ({ canvas, userEvent }) => {
await userEvent.click(canvas.getByRole('button', { name: 'Open dialog' }));
// Starts querying from the document
const dialog = screen.getByRole('dialog');
await expect(dialog).toBeVisible();
},
};编写故事
¥Composing stories
得益于 组件故事格式(一种基于 ES6 模块的文件格式),你还可以组合 play 功能,类似于其他现有的 Storybook 功能(例如 args)。例如,如果你想验证组件的特定工作流程,你可以编写以下故事:
¥Thanks to the Component Story Format, an ES6 module based file format, you can also combine your play functions, similar to other existing Storybook features (e.g., args). For example, if you wanted to verify a specific workflow for your component, you could write the following stories:
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { MyComponent } from './MyComponent';
const meta = {
component: MyComponent,
} satisfies Meta<typeof MyComponent>;
export default meta;
type Story = StoryObj<typeof meta>;
/*
* See https://storybook.js.org/docs/writing-stories/play-function#working-with-the-canvas
* to learn more about using the canvas to query the DOM
*/
export const FirstStory: Story = {
play: async ({ canvas, userEvent }) => {
await userEvent.type(canvas.getByTestId('an-element'), 'example-value');
},
};
export const SecondStory: Story = {
play: async ({ canvas, userEvent }) => {
await userEvent.type(canvas.getByTestId('other-element'), 'another value');
},
};
export const CombinedStories: Story = {
play: async ({ context, canvas, userEvent }) => {
// Runs the FirstStory and Second story play function before running this story's play function
await FirstStory.play(context);
await SecondStory.play(context);
await userEvent.type(canvas.getByTestId('another-element'), 'random value');
},
};通过组合故事,你可以重新创建整个组件工作流程,并可以发现潜在问题,同时减少你需要编写的样板代码。
¥By combining the stories, you're recreating the entire component workflow and can spot potential issues while reducing the boilerplate code you need to write.
