返回UI 測試手冊
React
章節
  • 簡介
  • 視覺化
  • 組合
  • 互動
  • 可訪問性
  • 使用者流程
  • 自動化
  • 工作流程
  • 結論

測試組件互動

學習如何模擬使用者行為並執行功能檢查

您撥動開關,燈卻沒有亮。可能是燈泡燒壞了,或者可能是電線故障。開關和燈泡透過牆壁內的電線連接在一起。

應用程式也是如此。表面是使用者看到並與之互動的 UI。在底層,UI 連接在一起,以促進資料和事件的流動。

當您建構更複雜的 UI(例如頁面)時,組件不僅僅負責渲染 UI,它們還會獲取資料並管理狀態。本章將教您如何使用電腦來模擬和驗證使用者互動。

該組件真的能正常運作嗎?

組件的主要任務是根據一組 props 渲染 UI 的一部分。更複雜的組件還會追蹤應用程式狀態,並將行為向下傳遞到組件樹中。

例如,組件將從初始狀態開始。當使用者在輸入欄位中輸入內容或點擊按鈕時,它會在應用程式內觸發事件。組件會響應該事件來更新狀態。然後,這些狀態變更會更新渲染的 UI。這就是互動的完整週期。

InboxScreen 上,使用者可以點擊星星圖示來釘選任務。或點擊核取方塊來封存它。視覺化測試確保組件在所有這些狀態下看起來都正確。我們還需要確保 UI 正在正確響應這些互動。

Storybook 中的組件測試如何運作?

測試互動是驗證使用者行為的廣泛模式。您提供模擬資料來設定測試情境,使用 Testing Library 模擬使用者互動,並檢查產生的 DOM 結構。

在 Storybook 中,這種熟悉的工作流程發生在您的瀏覽器中。這使得偵錯失敗變得更容易,因為您在與開發組件相同的環境(瀏覽器)中執行測試。

我們將從編寫一個故事開始,以設定組件的初始狀態。然後使用 play function 模擬使用者行為,例如點擊和表單輸入。最後,使用 Storybook 測試執行器 來檢查 UI 和組件狀態是否正確更新。

設定測試執行器

執行以下命令來安裝它

複製
yarn add --dev @storybook/test-runner

然後將測試任務新增到您專案的 package.json

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

最後,啟動您的 Storybook(測試執行器將針對您的本地 Storybook 實例執行)

複製
yarn storybook

重複使用故事作為組件測試案例

在前一章中,我們在 InboxScreen.stories.jsx 檔案中編目了 InboxScreen 組件的所有用例。這使我們能夠在開發期間進行外觀抽查,並透過視覺化測試捕獲回歸。這些故事現在也將為我們的組件測試提供動力。

複製
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 內。這段程式碼片段會附加到故事,並在故事渲染後執行。

讓我們加入我們的第一個組件測試,以驗證使用者可以釘選任務

複製
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 元素——故事的頂層容器。您可以將查詢範圍限定於此元素內,從而更輕鬆地找到 DOM 節點。

在本例中,我們正在尋找「Export logo」任務。然後找到其中的釘選按鈕並點擊它。最後,我們檢查按鈕是否已更新為未釘選狀態。

當 Storybook 完成故事渲染時,它會執行 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'
    );
  },
};

您現在應該看到這些情境的故事。Storybook 僅在您查看故事時執行組件測試。因此,您必須查看每個故事才能執行所有檢查。

每次進行變更時都手動查看整個 Storybook 是不切實際的。Storybook 測試執行器會自動執行該流程。它是一個獨立的實用程式(由 Playwright 提供支援),可執行您的所有測試並捕獲損壞的故事。

啟動測試執行器(在單獨的終端機視窗中)

複製
yarn test-storybook --watch

它將驗證是否所有故事都已渲染且沒有任何錯誤,以及是否所有斷言都已通過。如果測試失敗,您會獲得一個連結,該連結會在瀏覽器中打開失敗的故事。

總之,設定程式碼和測試都位於故事檔案中。使用 play function,我們像使用者一樣與 UI 互動。Storybook 組件測試結合了即時瀏覽器的直觀偵錯環境以及無頭瀏覽器的效能和腳本能力。

捕獲可用性問題

當您確保每個使用者都能使用您的 UI 時,您會影響企業財務並滿足 法律要求。這是一個雙贏的局面。下一章將示範如何利用故事的可移植性來簡化可訪問性測試。

讓您的程式碼與本章保持同步。在 GitHub 上查看 4aff15f。
這個免費指南對您有幫助嗎?發推文以示讚賞並幫助其他開發人員找到它。
下一章
可訪問性
透過整合工具快速回饋
✍️ 在 GitHub 上編輯 – 歡迎 PR!
加入社群
6,721位開發人員和持續增加中
為何選擇為何選擇 Storybook組件驅動 UI
開源軟體
Storybook - Storybook 繁體中文

特別感謝 Netlify 以及 CircleCI