繁體中文(台灣)
  • test
  • e2e test
  • integration test
  • dev
  • productivity
  • 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 庫,提供控制無頭 Chrome 或 Chromium 瀏覽器的高階 API。這使得它成為自動化瀏覽器交互進行端到端測試的理想選擇。

jest-puppeteer 是一個使 Puppeteer 進行端到端測試的 Jest 預設。它提供了一個簡單的 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 環境時,它偶爾會在多次執行後失敗。失敗信息顯示:

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

通常,有兩種常見方法可以達到這一點:

  1. 增加斷言等待時間

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

這種方法可以在一定程度上減少測試失敗的發生,但無法完全解決問題,因為我們無法確定數據抓取需要多長時間。

因此,我們只在確定所需等待時間時使用此方法,例如在 "懸停超過 2 秒後工具提示出現” 的場景中。

  1. 等待網絡請求完成後再進行斷言

這是正確的方法。儘管我們可能不知道數據請求需要多少時間,但等待網絡請求完成始終是安全的選擇。此時,我們可以使用 page.waitForNavigation({ waitUntil: 'networkidle0' }) 等待網絡請求完成:

這樣一來,在我們對 App 和其加載數據進行斷言之前,我們可以確保網絡請求已經完成。這確保測試能夠始終產生正確結果。

期待點擊特定按鈕

接下來,我們正在測試點擊某項目內的檢查按鈕的功能。

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

假設我們想點擊 "Read Logto get-started document" 項目的檢查按鈕。我們可以將其分解為兩步:

  1. 首先,獲取到該特定項目的引用。
  2. 然後,點擊位於該項目內的檢查按鈕。

如代碼所示,我們使用 toMatchElement 函數匹配項目其 itemName 設置為 "Read Logto get-started document"。然後,我們使用 readDocItem 引用來點擊位於其下方的文本內容為 'Check' 的按鈕。

重要的是注意,在獲取 readDocItem 時,使用的 CSS 選擇器是 li[class$=item]:has(div[class$=itemName])。這個選擇器匹配元素的根 li,而不是其內的 itemName。因為我們打算稍後在 li 標籤下面點擊 button

expect(readDocItem).toClick 的用法類似於 toMatchElement。在示例代碼中,我們傳遞 { text: 'Check' } 進一步匹配按鈕的 textContent。不過,根據你的需求,可以選擇是否匹配按鈕的文本內容。

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

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

在示例應用中,我們發現 "Read Logto get-started document" 項目在其備註中有一個指向 Logto 文檔的外部鏈接。以下是我們的測試代碼:

在代碼中,我們利用 toClick 點擊 itemNotes 中的鏈接。

隨後,我們使用 browser.waitForTarget 獲取一個新打開的 URL 為 "https://docs.logto.io/docs/tutorials/get-started" 的標籤。

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

此外,完成測試後,不要忘記關閉新打開的頁面。

期待從表單創建一個項目

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

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

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

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

不過,當我們的表單有許多字段需要填寫時,反復呼叫 toFill 並不方便。在這種情況下,我們可以使用如下方法在一次呼叫中填寫所有內容:

所提供的物件鍵值為輸入元素的 name 屬性。

總結

我們涵蓋了一個簡單的例子,以介紹使用 jest-puppeteer 進行端到端測試時的常見測試需求和相應的代碼編寫技術。我們希望這能幫助你迅速掌握如何編寫 jest-puppeteer 測試代碼的基礎知識。