簡介

v2.4 起,Nightwatch 通過 @nightwatch/storybook 外掛程式整合 Storybook,目前適用於 React。

Storybook 可以說是目前最流行的元件庫,在 React 方面擁有穩定的用戶群,且 Vue、Svelte、Angular 和其他 UI 庫的社群也在不斷增長。世界各地有數千個團隊正在使用它來構建具有可重複使用元件的 UI。

Storybook 的流行可能歸因於它滿足了當今前端開發人員的一個基本需求:為了能夠構建現代複雜的 UI,前端團隊需要能夠隔離地設計、構建和測試 UI 元件。Storybook 將這種技術稱為元件驅動開發 (CDD)。

元件驅動開發

Storybook 旨在通過提供引人注目的文件和測試工具,為管理大型元件庫提供完整的解決方案。每個元件都以其各種狀態和需求進行隔離渲染。

具有各種屬性和參數的元件的每個表示都稱為故事,屬於特定元件的所有故事都收集在 .stories.js[x](或 .ts[x])檔案中。故事以宣告式語法編寫,每個故事通過使用一組特定的 props 和模擬資料渲染元件來模擬特定的元件變體。

這是一個基本的元件故事範例

// 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 網頁應用程式分發,該應用程式載入現有的故事並在具有整合式元件文件和測試工具的複雜管理介面中顯示它們。

Storybook 應用程式可以在本機執行,也可以部署在網頁伺服器上。事實上,許多流行的元件庫都公開提供

如何測試元件故事

目前,Storybook 主要被用作前端開發人員和設計師之間協作的絕佳工具,用於記錄 UI 元件甚至整個頁面。

在測試進入並開始使一切複雜化之前,這一切都很好。當真正的樂趣開始時,因為為了有效地開發 UI 元件,還需要一種輕鬆測試它們的有效方法。

需要對元件進行自動化測試,以確保所有故事都使用指定的 props 和模擬資料正確渲染。此外,更複雜的元件可以具有需要模擬使用者操作和驗證行為的故事。

Storybook 開箱即用地為元件的幾種測試策略提供內建支援

圖片來源:https://storybook.dev.org.tw/docs/react/writing-tests/introduction

因此,有一些內建的測試支援,還有一個在底層使用 PlaywrightJestCLI 測試執行器。但是,它的優勢仍然更多地在整個網頁應用程式開發領域的設計方面。

為了對您構建的元件的程式碼品質有很強的信心,您需要擴展對元件測試的額外支援的故事,或將故事匯入到其他測試中。

將 Nightwatch 整合到您的 Storybook 中

Nightwatch 可以在 UI 元件設計+開發和 QA+測試之間彌合差距。為了在 Storybook 環境中添加對元件自動化測試的支援,您只需要安裝 Nightwatch 並使用測試功能擴展現有的故事即可。

在下一節中,我們將嘗試做到這一點。我們將使用一個公開在 Github 上提供的現有 Storybook,並執行以下步驟

  1. 安裝並設定 Nightwatch
  2. 使用 Nightwatch 執行現有的元件故事
  3. 使用 Nightwatch 測試功能擴展其中一個複雜的故事

那我們開始吧。我們將使用 世界糧食計畫署在 Github 上提供的 UI 套件,網址為 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。但是,在本文中,我們只會探討 Nightwatch 在 元件故事格式 之上新增的 test() 函數。您可以在 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 庫的集合,因此有很多機會進行實驗,甚至可以做出開源貢獻。

如果你在教學中走到這裡,我向你致敬!你現在已經準備好為一個在聯合國使用的開源專案做出貢獻,這相當了不起。

感謝您的閱讀,別忘了隨心所欲地戴上帽子,無論室內或室外。