介绍

v2.4 开始,Storybook 集成作为 Nightwatch 的一部分,通过 @nightwatch/storybook 插件实现,目前可用于 React。

Storybook 可以说是目前最受欢迎的组件库,在 React 中拥有稳定的用户基础,并且在 Vue、Svelte、Angular 和其他 UI 库中拥有不断增长的社区。数千个团队正在使用它来构建具有可重用组件的 UI。

Storybook 的流行可能归因于它识别了一种前端开发人员今天必不可少的需求:为了能够构建现代的复杂 UI,前端团队需要能够隔离地设计、构建和测试 UI 组件。Storybook 将这种技术称为 基于组件的开发 (CDD)。

基于组件的开发

Storybook 旨在通过提供引人注目的文档和测试工具,为管理大型组件库提供完整的解决方案。每个组件都以隔离的方式呈现,具有各种状态和要求。

每个具有各种属性和参数的组件表示形式称为 故事,属于特定组件的所有故事都收集在 .stories.js[x](或 .ts[x])文件中。故事是用声明性语法编写的,每个故事通过以特定属性集和模拟数据渲染组件来模拟特定组件变体。

这是一个基本的组件故事示例

// Histogram.stories.js|jsx

import React from 'react';

import { Histogram } from './Histogram';

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

const Template = (args) => <Histogram {...args} />;

export const Default = Template.bind({});
Default.args = {
  dataType: 'latency',
  showHistogramLabels: true,
  histogramAccentColor: '#1EA7FD',
  label: 'Latency distribution',
};

Storybook 作为 Node Web 应用程序分发,它加载现有的故事并在一个复杂的管理界面中显示它们,该界面集成了用于记录和测试组件的工具。

Storybook 应用程序可以在本地运行,也可以部署到 Web 服务器上。实际上,许多流行的组件库都 公开可用

如何测试组件故事

目前,Storybook 主要用作前端开发人员和设计师之间的一种协作工具,用于记录 UI 组件甚至整个页面。

这在测试出现并开始使一切变得复杂之前都很好。当真正的乐趣开始时,因为为了有效地开发 UI 组件,还需要一种简单而有效的测试方法。

需要对组件进行自动化测试,以确保所有故事都使用指定的属性和模拟数据正确渲染。此外,更复杂的组件可以有需要模拟用户操作和验证行为的故事。

Storybook 原生支持多种组件测试策略

图片来源:https://storybook.org.cn/docs/react/writing-tests/introduction

因此,有一些内置的测试支持,以及一个 CLI 测试运行器,它在内部使用 PlaywrightJest。但是,它的优势仍然更多地体现在整个 Web 应用程序开发谱的设计方面。

为了对构建的组件的代码质量充满信心,您需要扩展故事以添加对组件测试的额外支持,或者将故事导入其他测试中。

将 Nightwatch 集成到您的 Storybook 中

Nightwatch 可以帮助弥合 UI 组件设计+开发与 QA+测试之间的差距。为了在 Storybook 环境中添加对组件自动化的支持,您只需要安装 Nightwatch 并扩展现有的故事以添加测试功能。

在下一节中,我们将尝试这样做。我们将使用一个现有的 Storybook,它在 Github 上公开可用,并将完成以下步骤

  1. 安装和配置 Nightwatch
  2. 使用 Nightwatch 运行现有的组件故事
  3. 扩展其中一个复杂的故事,并添加 Nightwatch 测试功能

让我们开始吧。我们将使用 世界粮食计划署 的 UI 工具包,该工具包在 Github 上的地址为 https://github.com/wfp/designsystem

wfp.org/UIGuide

安装依赖项

我们首先需要 fork 该项目。我的 fork 位于 github.com/pineviewlabs/wfp-designsystem。我将克隆项目到本地并安装现有依赖项

--legacy-peer-deps 标志是让一些较旧的依赖项安装所必需的。如果您收到 npm audit 消息,您可以暂时忽略它们,因为这只是一个教程。

运行 Storybook

安装完所有内容后,您可以尝试在本地运行 Storybook。为此,请运行

npm run storybook

这将构建 Storybook 文件并在您的本地主机上运行该项目。

在撰写本指南时,WFP 设计工具包中使用的 Storybook 版本为 6.2。您可以尝试将其升级到至少 v6.5,但我已经尝试过,并且出现了一堆错误,因此目前我将坚持使用 6.2 以使所有内容协同工作。

安装 Nightwatch

Nightwatch 可以通过一条命令安装

npm init nightwatch@latest

这将提示您安装浏览器和测试文件的路径。我选择了 Chrome、Firefox、Safari 和 Edge。Edge 需要单独安装,但 init 工具将生成 Nightwatch 中所需的配置。

对于源文件夹路径,请输入以下内容

src/components/**/*.stories.js

对于基本 URL,请输入

http://localhost:9000/

我们还需要为 Nightwatch 安装 Storybook 插件

npm install @nightwatch/storybook

如果您使用的是较新的 NPM 版本,则可能需要再次传递 --legacy-peer-deps

最后一步是在 Nightwatch 中加载和配置插件。为此,请编辑 nightwatch.conf.js 并将以下内容添加到 src_folders 之后

// nightwatch.conf.js
module.exports = {
  // .. other settings
  plugins: ['@nightwatch/storybook'],

  '@nightwatch/storybook': {
    start_storybook: false, 
    storybook_url: 'http://localhost:9000/'
  }
}

Nightwatch 可以为我们在测试运行期间自动启动/停止 Storybook,但由于此特定版本的 Storybook 加载需要一些时间,因此我们将 start_storybook 设置为 false,并将手动运行它。

在 Nightwatch 中运行故事

@nightwatch/storybook 插件的设计方式是,可以在现有组件故事之上添加组件测试,而不是期望用户在测试中导入故事。在使用 Nightwatch 与 Storybook 时,测试本身就是 .stories 文件。

在我们的例子中,我们将运行现有的 src/component/**/*.stories.js 文件作为测试。但在我们这样做之前,我们需要配置最后一件事。

自定义 esbuild

Nightwatch 在内部使用 esbuild 来解析 JSX 文件,而该项目包含一些 esbuild 默认情况下不支持的语法特性。但是,我们可以通过使用 babel 启用编译来支持它们。

为此,请编辑 nightwatch.conf.js 并将以下内容添加到包含 '@nightwatch/storybook' 的行的后面

// nightwatch.conf.js
module.exports = {
  // other settings here...
  esbuild: {
    babel: {
      filter: /\.[cm]?js$/
    }
  }
}

添加 Babel

添加一个 babel.config.json 文件,内容如下,以便 esbuild 可以找到它

{
  "exclude": ["node_modules/**"],
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ],
  "plugins": [
    "@babel/plugin-proposal-export-namespace-from",
    "@babel/plugin-proposal-export-default-from"
  ]
}

运行 Nightwatch

现在是实际运行组件故事的时候了。该项目已经使用 Jest 和 Enzyme 编写了一些单元级类型的组件测试。我们不会去碰它们,而是使用实际的 .stories 文件。

我们将使用 Chrome 进行探索性测试运行,并将以 --serial 模式运行,以便我们可以观察浏览器

npx nightwatch --env chrome --serial

以串行模式运行项目中的所有故事可能需要一段时间。但是,我们可以尝试并行运行它们(您可以将“chrome”替换为“firefox”、“safari”或“edge”,具体取决于您安装了哪些浏览器)。

默认情况下,它将根据 CPU 核心数量选择测试工作程序的数量,但我们将将其设置为 4。我们还将以 --headless 模式运行它,这样浏览器就不会弹出并分散我们的注意力(请注意,Safari 不支持无头模式)

npx nightwatch --env chrome --workers=4 --headless

因此,到目前为止,我们希望在 WFP Storybook 项目中有一个可用的 Nightwatch 安装。目前,故事只会渲染,Nightwatch 将执行可见性检查以确保一切正常。

下一步是扩展其中一个故事并添加一些 Nightwatch 特定的功能。

使用 Nightwatch 测试扩展故事

我们将选择项目中较大的故事之一,并开始添加额外的测试功能。似乎 src/components/Form/Form.stories.js 在复杂性方面是一个不错的选择,所以我们将使用它。

让我们先单独运行它

npx nightwatch src/components/Form/Form.stories.js -e chrome

该文件中共有四个不同的故事,输出应类似于以下内容

[Form.stories.js component] Test Suite
───────────────────────────────────────────────────────────────────────
  Using: chrome (107.0.5304.110) on MAC OS X.


  Running "Default" story:
───────────────────────────────────────────────────────────────────────
  ✔ Passed [ok]: "components-forms-form--default.Default" story was rendered successfully.

  ✨ PASSED. 1 assertions. (1.078s)

  Running "Detailed Form" story:
───────────────────────────────────────────────────────────────────────
  ✔ Passed [ok]: "components-forms-form--detailed-form.DetailedForm" story was rendered successfully.

  ✨ PASSED. 1 assertions. (583ms)

  Running "Login" story:
───────────────────────────────────────────────────────────────────────
  ✔ Passed [ok]: "components-forms-form--login.Login" story was rendered successfully.

  ✨ PASSED. 1 assertions. (411ms)

  Running "Contact" story:
───────────────────────────────────────────────────────────────────────
  ✔ Passed [ok]: "components-forms-form--contact.Contact" story was rendered successfully.

  ✨ PASSED. 1 assertions. (513ms)

  ✨ PASSED. 4 total assertions (5.768s)

添加 Nightwatch 文件上传测试

Nightwatch 可以运行 Storybook 交互测试可访问性测试,以及支持 hooks api。但是,在本文中,我们只探讨 test() 函数,它是在 组件故事格式 之上添加的 Nightwatch。您可以在 Nightwatch 文档 上了解更多关于 Storybook 集成的信息。

我们将扩展 DetailedForm 故事,因此让我们编辑 src/components/Form/Form.stories.js 并将以下内容添加到包含 export const Login 的行(大约在第 400 行)之前

💡
test() 函数在 Node 上下文中运行,因此它可以访问文件系统,而 play() 函数则受到浏览器沙盒的限制。

test() 函数接收 Nightwatch browser API(可用于发出命令和运行断言)和一个 component 对象,该对象指向故事中的根元素,并且与 Nightwatch 命令和断言兼容。

在本例中,我们将使用 Nightwatch uploadFile() api 命令模拟文件的上传,并将验证文件拖放区元素是否已更新。

// src/components/Form/Form.stories.js
DetailedForm.test = async (browser, {component}) => {
  await browser.uploadFile('input[type="file"]', require.resolve('./README.mdx'));

  const fileElementContainer = await browser.findElement(`.${settings.prefix}--file-container`);
  const elementHasDescendants = await browser.hasDescendants(fileElementContainer);
  
  await browser.strictEqual(
    elementHasDescendants, true, 'The file dropzone element has been populated.'
  );
};

还需在文件顶部添加以下导入

// src/components/Form/Form.stories.js
import settings from '../../globals/js/settings';

我们现在可以只运行 DetailedForm 故事

npx nightwatch src/components/Form/Form.stories.js -e chrome --story=DetailedForm

希望一切顺利,输出应如下所示(顶部可能有一些警告,但这些警告并不重要)

[Form.stories.js component] Test Suite
───────────────────────────────────────────────────────────────────────
  Using: chrome (107.0.5304.110) on MAC OS X.


  Running "Detailed Form" story:
───────────────────────────────────────────────────────────────────────
  ✔ Passed [ok]: "components-forms-form--detailed-form.DetailedForm" story was rendered successfully.
  ✔ Passed [strictEqual]: The file dropzone element has been populated.

  ✨ PASSED. 2 assertions. (1.217s)

结论

目前就这些了。我可能会再写一篇类似的文章,使用不同的公共 Storybook 库。或者,您也许想贡献一篇?Storybook 的优秀团队最近开始在他们的 组件百科全书 中收集公共 Storybook 库,因此有很多机会进行实验,甚至可能做出开源贡献。

如果您能坚持到教程的最后,我向您致敬!您现在可以为 联合国 使用的开源项目做出贡献,这可是件大事。

感谢您的阅读,别忘了戴上您的帽子,无论室内还是室外。