繁體中文(香港)
  • 測試
  • 端到端測試
  • 集成測試
  • 開發
  • 生產力
  • jest
  • puppeteer

使用 jest-puppeteer 編寫端到端測試的快速指南

本文提供了一個使用 jest-puppeteer 編寫高效端到端測試的快速指南,並強調設置過程、常用 API 和通過一個簡單待辦事項應用程式來展示實際測試場景。

Yijun
Yijun
Developer

作為我們確保 Logto 質量和持續改進的承諾的一部分,我們使用 jest-puppeteer 進行端到端自動化測試。這讓我們能夠快速進行 Logto 的開發迭代而不會受到干擾。

在本文中,我們將分享我們使用一個簡單的待辦事項應用程式作為範例,編寫高效 jest-puppeteer 測試腳本的經驗。我們的目標是幫助你快速開始為自己的項目編寫 jest-puppeteer 測試代碼。

使用 jest-puppeteer 進行端到端測試的介紹

端到端測試是一種確保應用程式從用戶的角度正常工作的方式。為了實現這一目標,我們利用了兩個重要工具:JestPuppeteer

Jest 是一個流行的 JavaScript 測試框架,提供了一個用戶友好的 API 來編寫測試和斷言。它廣泛用於單元和集成測試。

Puppeteer 是由 Chrome 團隊開發的一個 Node.js 庫,提供了一個高層次的 API 來控制無頭的 Chrome 或 Chromium 瀏覽器。這使得它成為在端到端測試中自動化瀏覽器交互的理想選擇。

jest-puppeteer 是一個 Jest 預設,支持使用 Puppeteer 進行端到端測試。它提供了一個簡單的 API 來啟動新的瀏覽器實例並通過它們與網頁進行交互。

現在你已經對這些基本工具有了一個基本的瞭解,那麼我們來深入了解你的測試環境的設置。

設置你的測試環境

使用 jest-puppeteer 進行端到端測試時,設置測試環境是一個簡單的過程,主要包含三個步驟:

  1. 安裝依賴

在你的項目中(或創建一個新項目),打開終端並運行:

然後進入 node_modules/puppeteer 並安裝 Chromium(這是 Puppeteer 所需的):

  1. 配置 Jest

接下來,你需要配置 Jest 以無縫運行 Puppeteer。

在你的項目根目錄中創建一個 Jest 配置文件(例如,jest.config.js),如果你尚未擁有。在此配置文件中,指定 jest-puppeteer 作為預設:

你可以根據需要自定義此文件中的其他 Jest 設置。有關自定義 Jest 配置的更多資訊,請參閱 Jest 配置

  1. 編寫你的測試

在你的項目中創建測試文件,通常使用 .test.js 擴展名命名。Jest將自動發現並執行這些測試文件。

以下是來自 Jest 文檔 的一個示例:

然後執行命令運行測試:

通過這三個步驟,你將擁有一個配置完善的測試環境,可以使用 jest-puppeteer 進行端到端測試。

不過,請注意這只是基本範例。關於環境配置的更多詳細資訊,請參閱相關文檔:

常用 API

在接下來的步驟中,我們將依靠由 Jest、Puppeteer 和 jest-puppeteer 提供的 API 來協助我們進行測試。

Jest 主要提供用於組織測試和斷言預期結果的 API。關於具體細節,你可以在 文檔 中探索。

Puppeteer 的 API 主要旨在於瀏覽器交互,其對於測試的支持可能不那麼直接。你可以參考 Puppeteer 文檔 來瞭解它所提供的功能。在我們後續的測試範例中,將涵蓋一些常見的使用情況。

由於 Puppeteer 的 API 最初並非專為測試設計,因此使用它們來編寫測試可能會很具挑戰性。為了簡化編寫 Puppeteer 測試的過程,jest-puppeteer 包含一個嵌入的庫 expect-puppeteer。它提供一組簡明且用戶友好的 API。該庫在其 文檔 中有詳細介紹,下面的例子展示了各種有用的功能:

在接下來的範例中,我們將結合這些庫所提供的 API 來完成我們的測試場景。

現在是時候開始學習如何使用我們的簡單待辦事項應用程式編寫測試代碼了。

簡單的待辦事項應用程式

假設我們有一個簡單的待辦事項應用程式在 http://localhost:3000 上運行,該應用程式的主要 HTML 代碼如下:

在進行端到端測試時,我們的目標是涵蓋以下場景:

  1. 當我們訪問應用程式的 URL 時,應用程式及其數據應正確加載。
  2. 項目的查看按鈕應能點擊。
  3. 點擊項目備註中的外部鏈接應在新標籤中打開鏈接。
  4. 可以從表單中添加一個項目。

之後,我們將編寫測試代碼來驗證這些場景。

期待應用程式及其數據已加載

我們認為應用程式已成功加載的條件是:

  1. 在訪問應用程式的 URL 後,頁面上應存在ID為 "app" 的元素。
  2. 應用程式內的數據應正確渲染。

所以,我們編寫了以下測試代碼:

在代碼中:

  • page.goto 相當於在瀏覽器中輸入 "http://localhost:3000",允許你導航至任何 URL。
  • page.waitForSelector 用於等待某個CSS選擇器符合特定元素,默認等待時間為 30 秒。
  • expect(page).toMatchElement 是從 expect-puppeteer 庫中來的,類似於 page.waitForSelector,但它也支持匹配元素內的文本。此外,toMatchElement 的默認超時僅為 500 毫秒。

乍看之下似乎完美,然而在運行這個測試並將其部署到 CI 環境中時,多次執行後偶爾會失敗。失敗消息顯示:

根據失敗的信息和該測試在大多數時候通過的事實,我們可以推斷應用程式請求的數據可能在應用程式加載後的頭500毫秒內並不總是返回。因此,我們希望在斷言之前等待數據加載。

通常,有兩種常見的方法來實現這一點:

  1. 增加斷言的等待時間

從錯誤訊息中可以看到,toMatchElement的默認等待時間設置為 500毫秒。我們可以通過向函數添加 timeout 選項來增加等待時間,這樣做:

這種方法可以在某種程度上減少測試失敗的發生,但它並不能完全解決問題,因為我們無法確定數據加載所需的準確時間。

因此,我們僅在確定所需等待時間時使用此方法,例如在像是 "懸停在元素上超過 2 秒後顯示提示" 的情境下。

  1. 在斷言之前等待網絡請求完成

這是正確的方法。雖然我們可能不知道數據請求會花多長時間,但等待網絡請求完成始終是一個安全的選擇。此時,我們可以使用 page.waitForNavigation({ waitUntil: 'networkidle0' }) 來等待網絡請求完成:

如此,在我們為應用程式及其加載數據執行斷言之前,我們能夠確保網絡請求已經結束。這確保了測試始終產生正確的結果。

期待點擊特定按鈕

接下來,我們正在測試點擊一個項目中 "查看" 按鈕的功能。

在示例應用程式中,我們注意到所有項目都具有相同的結構。它們的 "查看" 按鈕具有 CSS 選擇器 li[class$=item] > button,且按鈕文本總是 "查看"。這意味著我們不能直接指定要點擊哪個項目的按鈕。因此,我們需要一個新解決方案。

假設我們想要點擊 "閱讀 Logto 快速入門文檔" 項目中的查看按鈕。我們可以將其分為兩個步驟:

  1. 首先,獲得該特定項目的引用。
  2. 然後,點擊該項目中的 "查看" 按鈕。

如代碼所示,我們使用 toMatchElement 函數來匹配 itemName 為 "閱讀 Logto 快速入門文檔" 的項目。然後,我們使用 readDocItem 引用點擊其下面帶有文本內容 "查看" 的按鈕。

值得注意的是,在獲得 readDocItem 時,使用的 CSS 選擇器是 li[class$=item]:has(div[class$=itemName])。此選擇器匹配項目的根 li 元素,而不是其中的 itemName,因為我們打算稍後點擊 li 標記下的 button

expect(readDocItem).toClick 的使用類似於 toMatchElement。在示例代碼中,我們傳入 { text: '查看' } 進一步匹配按鈕的 textContent。然而,你可以根據需要選擇是否匹配按鈕的文本內容。

期待外部鏈接在新標籤中打開

接下來,我們希望測試在點擊項目備註中的外部鏈接時,我們是否能夠在新標籤中打開鏈接。

在示例應用程式中,我們發現 "閱讀 Logto 快速入門文檔" 項目在其備註中有一個指向 Logto 文檔的外部鏈接。這是我們的測試代碼:

在代碼中,我們使用 toClickitemNotes 中點擊連結。

隨後,我們使用 browser.waitForTarget 捕捉一個新打開的標籤,該標籤的網址為 "https://docs.logto.io/docs/tutorials/get-started"。

獲得標籤後,我們使用 target.page() 獲得 Logto 文檔頁面的實例,然後進一步檢查頁面是否已加載。

還有,記得在完成我們的測試後關閉新打開的頁面。

期待從表單中創建一個項目

現在,我們要測試添加項目的功能。

我們需要在表單中填寫項目名稱和備註,點擊 "添加" 按鈕並檢查我們添加的內容是否出現在列表中:

你會注意到我們使用 expect(page).toFill(inputSelector, content) 函數填寫表單內容。此函數將輸入中的所有內容替換為 content

如果你想在不替換現有內容的情況下添加一些字符,你可以使用 page.type(selector, content) 直接將所需內容附加到輸入中。

然而,當我們的表單有很多字段需要填寫時,多次調用 toFill 並不方便。在這種情況下,我們可以使用以下方法一次性將所有內容填寫:

提供的對象鍵是輸入元素的 name 屬性。

總結

我們已經通過一個簡單的示例介紹了使用 jest-puppeteer 進行端到端測試時的常見測試需求和相應的代碼編寫技巧。我們希望它能幫助你快速掌握編寫 jest-puppeteer 測試代碼的基礎。