返回UI 测试手册
React
章节
  • 简介
  • 可视化
  • 组合
  • 交互
  • 可访问性
  • 用户流程
  • 自动化
  • 工作流程
  • 结论

测试组件交互

了解如何模拟用户行为并运行功能检查

你拨动开关,灯没有亮。可能是灯泡烧坏了,也可能是电线故障。开关和灯泡通过墙壁内的电线相互连接。

应用也是如此。表面是用户看到并与之交互的 UI。底层则是 UI 连接起来以促进数据和事件的流动。

随着你构建更复杂的 UI(例如页面),组件不仅负责渲染 UI。它们还会获取数据并管理状态。本章将教你如何使用计算机模拟和验证用户交互。

该组件真的有效吗?

组件的主要任务是根据一组 props 渲染 UI 的一部分。更复杂的组件还会跟踪应用程序状态并将行为传递到组件树下方。

例如,组件将从初始状态开始。当用户在输入字段中输入内容或点击按钮时,这会触发应用内的事件。组件会响应此事件更新状态。这些状态变化随后会更新渲染的 UI。这是一个交互的完整周期。

InboxScreen 上,用户可以点击星形图标来固定任务。或者点击复选框来存档任务。可视化测试可确保组件在所有这些状态下看起来正确。我们还需要确保 UI 正确响应这些交互。

Storybook 中的组件测试如何工作?

测试交互是验证用户行为的常用模式。你提供模拟数据来设置测试场景,使用 Testing Library 模拟用户交互,并检查生成的 DOM 结构。

在 Storybook 中,这个熟悉的工作流程在你的浏览器中进行。这使得调试失败更容易,因为你在与开发组件相同的环境(浏览器)中运行测试。

我们将首先编写一个 story 来设置组件的初始状态。然后使用 play function 模拟用户行为,例如点击和表单输入。最后,使用 Storybook 的 test runner 来检查 UI 和组件状态是否正确更新。

设置测试运行器

运行以下命令安装它

复制
yarn add --dev @storybook/test-runner

然后将测试任务添加到你的项目的 package.json 文件中

{
  "scripts": {
    "test-storybook": "test-storybook"
  }
}

最后,启动你的 Storybook(测试运行器将针对你的本地 Storybook 实例运行)

复制
yarn storybook

将 stories 复用为组件测试用例

在上一章中,我们在 InboxScreen.stories.jsx 文件中列出了 InboxScreen 组件的所有用例。这使得我们在开发过程中可以抽查外观,并通过可视化测试发现回归。这些 stories 现在也将支持我们的组件测试。

复制
src/InboxScreen.stories.jsx
import { http, HttpResponse } from 'msw';

import InboxScreen from './InboxScreen';

import { Default as TaskListDefault } from './components/TaskList.stories';

export default {
  component: InboxScreen,
  title: 'InboxScreen',
};

export const Default = {
  parameters: {
    msw: {
      handlers: [
        http.get('/tasks', () => {
          return HttpResponse.json(TaskListDefault.args);
        }),
      ],
    },
  },
};

export const Error = {
  args: {
    error: 'Something',
  },
  parameters: {
    msw: {
      handlers: [
        http.get('/tasks', () => {
          return HttpResponse.json([]);
        }),
      ],
    },
  },
};

使用 play function 编写组件测试

Testing Library 提供了方便的 API 用于模拟用户交互——点击、拖动、触摸、输入等。而 Vitest 则提供了断言工具。我们将使用 Storybook 集成的这些工具版本来编写测试。因此,你可以获得熟悉的开发者友好语法来与 DOM 交互,并带有额外的遥测功能来帮助调试。

测试本身将包含在一个 play function 中。这段代码片段附加到 story 上,并在 story 渲染后运行。

让我们添加第一个组件测试,以验证用户可以固定任务

复制
src/InboxScreen.stories.jsx
import { http, HttpResponse } from 'msw';

import InboxScreen from './InboxScreen';

import { Default as TaskListDefault } from './components/TaskList.stories';

import { expect, userEvent, findByRole, within } from '@storybook/test';

// ... code omitted for brevity ...

export const PinTask = {
  parameters: {
    ...Default.parameters,
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const getTask = (id) => canvas.findByRole('listitem', { name: id });

    const itemToPin = await getTask('task-4');
    // Find the pin button
    const pinButton = await findByRole(itemToPin, 'button', { name: 'pin' });
    // Click the pin button
    await userEvent.click(pinButton);
    // Check that the pin button is now a unpin button
    const unpinButton = within(itemToPin).getByRole('button', {
      name: 'unpin',
    });
    await expect(unpinButton).toBeInTheDocument();
  },
};

💡 @storybook/test 包替代了 @storybook/jest@storybook/testing-library 测试包,提供了更小的包大小和基于 Vitest 包的更直接的 API。

每个 play function 都会接收 Canvas 元素——story 的顶级容器。你可以将查询范围限定在此元素内,从而更容易找到 DOM 节点。

在我们的例子中,我们正在寻找“导出 Logo”任务。然后在其中找到固定按钮并点击它。最后,我们检查按钮是否已更新为未固定状态。

当 Storybook 完成 story 渲染后,它会执行 play function 中定义的步骤,与组件交互并固定任务——类似于用户操作的方式。如果你查看你的交互面板,你会看到逐步的流程。它还提供了一套便捷的 UI 控制,用于暂停、恢复、回退和单步执行每个交互。

使用测试运行器执行测试

现在我们已经完成了第一个测试,接下来我们将添加用于存档和编辑任务功能的测试。

复制
src/InboxScreen.stories.jsx
// ... code omitted for brevity ...

export const ArchiveTask = {
  parameters: {
    ...Default.parameters,
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const getTask = (id) => canvas.findByRole('listitem', { name: id });

    const itemToArchive = await getTask('task-2');
    const archiveButton = await findByRole(itemToArchive, 'button', {
      name: 'archiveButton-2',
    });
    await userEvent.click(archiveButton);
  },
};

export const EditTask = {
  parameters: {
    ...Default.parameters,
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const getTask = (id) => canvas.findByRole('listitem', { name: id });

    const itemToEdit = await getTask('task-5');
    const taskInput = await findByRole(itemToEdit, 'textbox');
    await userEvent.type(taskInput, ' and disabled state');
    await expect(taskInput.value).toBe(
      'Fix bug in input error state and disabled state'
    );
  },
};

现在你应该会看到这些场景的 stories。Storybook 只在你查看 story 时运行组件测试。因此,你必须审查每个 story 来运行所有检查。

每次更改时手动审查整个 Storybook 是不现实的。Storybook test runner 自动化了这一过程。它是一个独立的实用工具——由 Playwright 提供支持——它运行所有测试并捕获损坏的 stories。

启动测试运行器(在一个单独的终端窗口中)

复制
yarn test-storybook --watch

它将验证所有 stories 是否都无错误地渲染,并且所有断言都已通过。如果测试失败,你会获得一个链接,可以在浏览器中打开失败的 story。

总之,设置代码和测试都位于 stories 文件中。使用 play function,我们像用户一样与 UI 进行了交互。Storybook 组件测试结合了实时浏览器的直观调试环境以及无头浏览器的性能和可脚本性。

捕获可用性问题

当你确保你的 UI 对每个用户都可用时,你就影响了业务财务并满足了法律要求。这是一个双赢。下一章将演示如何利用 stories 的可移植性来简化可访问性测试。

让你的代码与本章保持同步。在 GitHub 上查看 4aff15f。
这本免费指南对你有帮助吗?发条推文赞美它,帮助其他开发者找到它。
下一章
可访问性
通过集成工具获得快速反馈
✍️ 在 GitHub 上编辑 – 欢迎提交 PR!
加入社区
6,975名开发者等
原因为何选择 Storybook组件驱动的 UI
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI