返回博客

交互式故事(测试版)

使用 play 函数模拟用户行为

loading
Varun Vachhar
@winkerVSbecks
最后更新于

独立构建组件可以让你对其进行压力测试,从而找到边缘情况。使用 Storybook,你可以通过向组件提供 props 来为每个这些情况编写故事。

但并非所有组件变体都能仅通过 props 重现。有些 UI 状态只能通过用户交互实现,例如下拉菜单、模态框和隐藏的表单元素。过去,你必须手动与组件交互来检查这些状态是否正确显示。

我很高兴地宣布**交互式故事(测试版)**,它允许你在故事渲染后模拟用户行为。这省去了手动操作组件的繁琐工作。

  • ✅ 在真实浏览器中运行
  • ⚡️ 无需等待,稳定可靠
  • 🐙 由 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 Linter

交互式故事和交互面板都由增强版的 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 测试版中提供。这个实验性版本为我们在八月宣布的更广泛的交互测试功能奠定了基础。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 HengeveldDeen DennoYann BragaMichael ShilmanTom ColemanMichael ArestadDominic Nguyen 开发,并得到了整个 Storybook 社区的反馈。

如果 Storybook 让你的 UI 开发工作流程更轻松,请帮助 Storybook 变得更好。你可以贡献新功能、修复 bug 或改进文档。在 Discord 上加入我们,或直接在 Github 上参与。要获取项目更新和早期功能访问权限,请订阅下方的 Storybook 邮件列表。

订阅 Storybook 邮件列表

获取最新新闻、更新和版本发布信息

7,180位开发者及更多

我们正在招聘!

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

查看职位

热门文章

Storybook 和 Next.js 入门

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

使用 Storybook 文档化你的设计系统的 4 种方法

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

Storybook 按需架构

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

特别感谢 Netlify CircleCI