返回博客

互动故事 (beta)

使用 play 函数模拟用户行为

loading
Varun Vachhar
@winkerVSbecks
最后更新

在隔离环境中构建组件可以让你进行压力测试,以发现边缘情况。 使用 Storybook,你可以通过为组件提供 props 来为每种情况编写故事。

但并非所有组件变体都可以仅通过 props 重现。 某些 UI 状态只能通过用户交互才能达到,例如下拉菜单、模态框和隐藏的表单元素。 过去,你需要手动与组件交互来检查这些状态是否看起来正确。

我很高兴地宣布 **互动故事 (beta)**,它允许你模拟用户行为在故事渲染后运行。 它消除了手动调整组件的繁琐工作。

  • ✅ 在真实浏览器中运行
  • ⚡️ 无需等待且无抖动
  • 🐙 由 Testing Library 驱动
  • 🛠 低维护成本
  • 🔍 快速可视化调试

用于交互的 Play 函数

故事以结构化的方式隔离和捕获组件状态。 在开发组件时,你可以快速浏览各个故事以验证外观和感觉。

每个故事都指定了重现特定状态所需的所有输入。 你甚至可以模拟上下文和 API 调用。 这使你可以处理组件的大多数用例。

但是需要用户交互的状态呢?

例如,单击按钮以打开/关闭对话框、拖动列表项以重新排序或填写表单以检查验证错误。 要测试这些行为,你必须像用户一样与组件交互。

互动故事使你能够使用 play 函数自动化这些交互。 这些是代码片段,脚本化了人类与组件交互的确切步骤。 然后在故事渲染后立即执行。

由 Testing Library 驱动

这些交互是使用 Storybook 仪器化的 Testing Library 版本编写的。 这为你提供了熟悉的、开发者友好的语法来与 DOM 交互,但带有额外的遥测功能来帮助调试。 这是一个基本示例,演示了单击按钮以打开对话框。

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' })
  );
};

如果你可以在 Storybook 中渲染它,那么你可以为其编写一个互动故事。

互动故事使用框架无关的 DOM API。 这意味着,无论你使用哪个框架,你都可以编写一个 play 函数来操作 UI 并自动化用户行为。 唯一的注意事项是 Web Components,因为 Testing-Library 尚不支持 shadow DOM。

使用 GUI 更快地调试

大多数团队已经使用 Testing Library 为其组件编写功能测试。 这些测试使用像 Jest 这样的测试运行器执行。 它们在 Node 中使用 JSDOM 运行。 如果测试失败,你得到的只是一堆 HTML 来调试。

使用 CLI 调试 UI 问题就像在 Playstation 5 上玩文字冒险游戏。 它有效,但如果你能看到一些图形会好得多。

互动故事使用 Storybook 特定的 Testing Library 版本。 它在浏览器中运行,并为你可视化整个 play 函数。 可以将其视为 Testing Library 的 GUI。

如果故事失败,交互面板会突出显示损坏的步骤。 然后,你可以直接在浏览器中使用你最喜欢的开发者工具调试 UI。 而不是在 JSDOM 中使用不透明的 CLI。 能够看到和检查 UI 使调试变得轻而易举。

Storybook 代码检查器

互动故事和交互面板都由仪器化的 Testing Library 版本启用。 这意味着你必须使用来自 @storybook/testing-library 的辅助函数,并确保你的故事是使用正确的语法编写的。 我们正在开发一个 ESLint 插件来自动强制执行这些检查。 它将与 Storybook 6.4 一起发布。 敬请关注!

让我们通过一个演示来了解整个工作流程。

互动故事示例

考虑 Taskbox 应用程序——一个类似于 Asana 的任务管理应用程序。 它显示用户可以固定、存档和编辑的任务列表。

故事看起来像这样

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);
};

该故事使用 MSW 插件来模拟 /tasks API 调用。 该模拟数据用于渲染任务列表,交互存在于 play 函数内部。

今天就试试并分享你的反馈

互动故事现已在 Storybook 6.4 beta 版中提供。 这个实验性版本为我们在 8 月份宣布的更广泛的交互测试功能奠定了基础。 Storybook 6.4 让我们有机会在 Storybook 6.5 的完整发布之前测试和稳定此功能。 试用一下,并与我们分享你的反馈。

通过在项目根目录运行以下命令来安装它。

npx sb@next init

要升级现有项目

npx sb upgrade --prerelease

然后安装与互动故事相关的依赖项

yarn add -D @storybook/addon-interactions @storybook/testing-library

并在 .storybook/main.js 中启用调试器。 请注意,@storybook/addon-interactions 必须在 @storybook/addon-actions@storybook/addon-essentials 之后列出。

// .storybook/main.js
module.exports = {
 addons: ['@storybook/addon-interactions'],
};

参与进来

互动故事使测试组件的功能方面变得更容易。 你通过 play 函数编写交互脚本,Storybook 会为你自动运行它们。 方便的交互面板可视化 play 函数中的每个交互。

互动故事功能由 Gert Hengeveld、Deen Denno、Yann Braga、Michael Shilman、Tom Coleman、Michael Arestad 和 Dominic Nguyen 开发,并获得了整个 Storybook 社区的反馈。

如果 Storybook 使你的 UI 开发工作流程更轻松,请帮助 Storybook 变得更好。 你可以贡献新功能、修复错误或改进文档。 在 Discord 上加入我们,或者直接参与 Github。 要获取项目更新和抢先体验功能,请在下面注册 Storybook 的邮件列表。

加入 Storybook 邮件列表

获取最新的新闻、更新和发布

6,730位开发者以及更多

我们正在招聘!

加入 Storybook 和 Chromatic 背后的团队。 构建被数十万开发者在生产环境中使用的工具。 远程优先。

查看职位

热门文章

开始使用 Storybook 和 Next.js

通过四个简单的步骤将 Storybook 与 Next.js 集成
loading
Michael Chan

使用 Storybook 记录你的设计系统的 4 种方法

如何一起展示 UI 组件、规范和使用指南
loading
Varun Vachhar

Storybook 按需架构

构建的 Storybook 的构建体积缩小 3 倍,加载时间更快
loading
Tom Coleman
加入社区
6,730位开发者以及更多
为什么为什么选择 Storybook组件驱动的 UI
文档指南教程更新日志遥测
社区插件参与进来博客
展示探索项目组件词汇表
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI