測試是軟體開發中的必修課,有許多可行的工具來完成這項任務。本文將探討 Puppeteer 如何使自動化測試變得簡單而有效,並將教您如何為 React 應用程式編寫簡單、高效的 E2E 測試,以及如何使用 Puppeteer 從使用者角度進行測試。
軟體測試是軟體開發生命週期(SDLC)的一個重要方面。對應用程式進行測試勢在必行,因為這樣做可以幫助您及早發現編寫程式碼中的錯誤和缺陷,並使您能夠構建符合預期的高質量標準軟體。
然而,許多組織仍然嚴重依賴效率低下的手動測試方法。統計資料為實施更多的自動化測試提供了令人信服的理由。研究表明,56% 的缺陷是通過內部測試發現的,但只有 5% 的公司進行了完全自動化的測試。三分之二的公司以 75:25(手動:自動化)或 50:50 的比例進行測試。
預計 2021-2026 年間,軟體測試市場將以 7% 的複合年增長率增長,因此測試自動化是跟上市場步伐的關鍵。一項研究發現,79% 的終端使用者在 2019 年仍會遇到錯誤,這表明需要更好的測試。70% 的 IT 領導者肯定自動化測試能帶來更高的效率,因此自動化趨勢只增不減。Puppeteer 等解決方案可實現簡化的端到端(E2E)測試,幫助組織和公司提高軟體質量和使用者體驗。
什麼是 Puppeteer?
Puppeteer 是谷歌開發的一個 Node.js 庫,用於自動化前端測試過程。它提供高階 API,可通過 DevTools 協議控制 Chrome 瀏覽器或基於 Chromium 的瀏覽器。它允許測試人員執行無頭和有頭操作。
無頭與有頭瀏覽器操作
無頭瀏覽器是一種沒有圖形使用者介面(GUI)的瀏覽器。它們通過命令列介面或網路通訊執行。- 維基百科
Puppeteer 允許以無頭模式(不顯示瀏覽器使用者介面)或有頭模式(顯示瀏覽器使用者介面)控制 Chrome 或 Chromium。雖然無頭模式是預設模式,但有頭模式也有助於除錯。與無頭模式瀏覽器相比,網站也更難檢測到由指令碼控制的有頭模式瀏覽器。
Puppeteer 的應用領域:
- 自動化大多數使用者介面測試,包括鍵盤和滑鼠移動
- 執行端到端測試
- 網頁抓取和刮擦
- 生成螢幕截圖和 PDF
- 捕捉網站的時間線軌跡,幫助診斷效能問題
- 自動提交表單
什麼是 Jest?
Jest 是一個開源 JavaScript 測試框架,旨在測試 React 和 React Native 網路應用程式。
通常,軟體前端的 E2E 測試過於繁瑣,因為需要進行大量耗時的配置。然而,使用 Jest 進行測試可以最大限度地減少單元測試所需的配置,從而降低前端測試的複雜性。
設定專案
在本節中,你將使用 Puppeteer 和 Jest 配置一個 React 應用程式。首先,你將使用 CRA(建立 React 應用程式)引導一個新的 React 應用程式。
執行以下命令:
npx create-react-app react-puppeteer-test
接下來,建立專案目錄和 react-puppeteer-test
應用程式後,導航到新建立的目錄,並在終端執行以下命令安裝以下軟體包:
- Jest – 我們的測試執行程式
- Puppeteer – 我們的瀏覽器自動化工具
- Jest-Puppeteer – 執行 Puppeteer 測試的 Jest 外掛
npm install puppeteer jest jest-puppeteer
設定好應用程式後,在終端中輸入以下任一命令執行應用程式:
yarn start
或使用 npm:
npm start
伺服器應在瀏覽器 http://localhost:3000/ 中啟動,如下圖所示:
配置 Jest 設定
Jest 預設情況下執行良好,但有時您需要更多配置功能。首先,在專案根目錄下建立一個名為 jest.config.js
的檔案,設定 Jest 配置。用以下程式碼段填充該檔案:
module.exports = { preset: "jest-puppeteer", testMatch: ["**/src/__tests__/**/*.test.js"], verbose: true, };
此配置指示 Jest 使用 jest-puppeteer
預設,並將測試放在 __tests__
資料夾中。
在 package.json
檔案中新增相應的指令碼:
"scripts": { "test": "jest" }
該命令指示 Jest 處理測試指令碼。您已在應用程式中成功配置了 Jest。
配置 Puppeteer 設定
接下來,在專案根資料夾中建立一個名為 jest-puppeteer.config.js
的檔案,並貼上以下程式碼段以建立 Puppeteer 配置:
module.exports = { launch: { headless: process.env.HEADLESS !== "false", slowMo: process.env.SLOWMO ? parseInt(process.env.SLOWMO, 10) : 0, devtools: process.env.DEVTOOLS === "true", product: "chrome", args: [ "--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", "--disable-accelerated-2d-canvas", "--disable-gpu", "--window-size=1920,1080", ], }, };
該配置規定在無頭模式下執行 Puppeteer,避免使用沙箱,禁用 GPU 並設定視窗大小。它還允許自定義瀏覽器產品(Chrome 或 Firefox),並引入了減緩測試執行速度以提高可視性的選項。
使用 Puppeteer 的測試示例
關於使用 Puppeteer 和 Jest 進行的基本測試,請參考以下使用示例。
在本教程中,我將指導你使用 Puppeteer 進行第一次自動測試。如果你閱讀了本節內容,說明你已經在專案中安裝並設定了 Puppeteer。如果還沒有,請按照上面的說明操作。如果你已經安裝,那就讓我們開始吧。
建立測試檔案。你必須建立實際的測試檔案來進行測試。為此,您必須在某些目錄中合理安排測試檔案的結構。在終端執行以下命令:
cd src && mkdir __tests__ && cd __tests__ && touch homepage.test.js
上述命令的作用是
- 將目錄更改為
src
資料夾; - 將目錄更改為
__tests__
資料夾; - 建立一個名為
homepage.test.js
的新檔案
// src/__tests__/homepage.test.js const puppeteer = require("puppeteer"); describe("OpenReplay.com page", () => { let browser; let page; beforeAll(async () => { browser = await puppeteer.launch(); page = await browser.newPage(); }); it("should contain text", async () => { await page.goto("https://openreplay.com/", { waitUntil: 'networkidle2' }); await page.waitForSelector(".display-4"); const text = await page.$eval(".display-4", (e) => e.textContent); expect(text).toContain(`Session replay`); }); afterAll(() => browser.close()); });
上面這段程式碼的作用是
- 首先描述您的 E2E 測試
- 瀏覽測試網站
- 您正在測試
display-4
類是否包含網站上的Session replay
文字。beforeAll
函式啟動 Puppeteer 例項,建立瀏覽器和頁面。測試使用頁面的選擇器檢查是否存在指定文字。
這可以作為建立自定義測試的模板。現在,讓我們來探討一個實際場景。
真實世界示例:測試登入表單
考慮一個登入表單元件:
// App.js import { useState } from "react"; import "./App.css"; function App() { const [isUserLoggedIn, setIsUserLoggedIn] = useState(false); const [error, setError] = useState(false); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const login = (event) => { event.preventDefault(); if (email === "admin@openreplay.com" && password === "password") { setIsUserLoggedIn(true); } else { setIsUserLoggedIn(false); setError(true); setTimeout(() => { setError(false); }, 4000); } }; return ( <div className="App"> <div className="form-wrapper"> <h1 className="form-header">Welcome back</h1> {!isUserLoggedIn && ( <form className="form" onSubmit={login}> {error && ( <p className="form-error-text"> Invalid email or password </p> )} <input type="email" required placeholder="Email Address" className="form-input form-input__email" onChange={(e) => { setEmail(e.target.value); }} name="emailAddress" /> <input type="password" required placeholder="Password" className="form-input form-input__password" onChange={(e) => { setPassword(e.target.value); }} name="password" /> <button type="submit" className="form-submit-button"> Submit </button> </form> )} {isUserLoggedIn && ( <p className="form-success-message">Login successful.</p> )} </div> </div> ); } export default App;
在上面的程式碼中,我們模擬了一個登入場景:
- 我們使用 useState 鉤子初始化了狀態變數
isUserLoggedIn
、error
、email
和password
。 login
函式處理表單提交、檢查憑證並相應更新狀態。- JSX 會根據使用者的登入狀態渲染一個包含電子郵件、密碼輸入和提交按鈕的表單。
- 如果登入嘗試失敗,會顯示一條錯誤資訊,並在 4 秒後消失。
- 只有當使用者名稱是
admin@openreplay.com
且密碼是password
時,登入才會成功。 - 使用者登入後會顯示一條成功資訊。
- 該元件將作為預設匯出元件匯出,供應用程式其他部分使用。
// App.test.js import puppeteer from "puppeteer"; describe("App.js", () => { let browser; let page; beforeAll(async () => { browser = await puppeteer.launch(); page = await browser.newPage(); }); it("shows a success message after submitting a form", async () => { await page.goto("http://localhost:5000"); await page.waitForSelector(".form-header"); await page.click(".form-input__email"); await page.type(".form-input__email", "admin@openreplay.com"); await page.click(".form-input__password"); await page.type(".form-input__password", "password"); await page.click(".form-submit-button"); await page.waitForSelector(".form-success-message"); const text = await page.$eval( ".form-success-message", (e) => e.textContent ); expect(text).toContain("Login successful."); }); it("shows an error message if authentication fails", async () => { await page.goto("http://localhost:5000"); await page.waitForSelector(".form-header"); await page.click(".form-input__email"); await page.type(".form-input__email", "admin@openreplay.com"); await page.click(".form-input__password"); await page.type(".form-input__password", "password123"); await page.click(".form-submit-button"); await page.waitForSelector(".form-error-text"); const text = await page.$eval(".form-error-text", (e) => e.textContent); expect(text).toContain("Invalid email or password"); }); afterAll(() => browser.close()); });
上述測試示例 ⬆ 演示了使用 Puppeteer 和 Jest 測試登入表單。它包括導航到應用程式、與表單元素互動以及驗證預期結果。
- 它包括
beforeAll
塊中的設定程式碼、啟動 Puppeteer 瀏覽器例項以及在任何測試前建立新頁面。 - 第一個測試(
it
)檢查使用有效憑證提交表單後是否顯示成功訊息(Login successful.
)。 - 在第一個測試中,表單的互動方式是導航到指定的 URL,填寫電子郵件和密碼欄位,單擊提交按鈕,然後驗證是否出現成功訊息。
- 第二個測試將檢查在嘗試使用無效憑據進行身份驗證時是否顯示錯誤訊息。
- 與第一個測試類似,第二個測試通過導航到 URL、輸入無效電子郵件和密碼、單擊提交按鈕以及驗證是否出現錯誤訊息(
Invalid email or password
)來與表單互動。AfterAll 塊確保在完成所有測試後關閉 Puppeteer 瀏覽器例項。
探索 Puppeteer 的除錯功能
Puppeteer 提供了多種除錯選項,可用於 React 應用程式的端到端測試。讓我們來探索這些選項,並演示如何將它們整合到現有測試中。
在無頭模式下執行
Puppeteer 預設以無頭模式啟動 Chromium 瀏覽器,這意味著沒有可見的使用者介面。這非常適合自動化測試和伺服器環境。不過,如果你想在測試執行期間觀察瀏覽器,可以通過設定 headless: false
來啟動完整版瀏覽器:
beforeAll(async () => { browser = await puppeteer.launch({ headless: false }); page = await browser.newPage(); });
執行測試時,將顯示一個 Chromium 瀏覽器視窗。
減慢測試執行速度
在無頭模式下,某些操作(如表單提交)可能發生得太快,無法觀察。為了解決這個問題,Puppeteer 提供了 slowMo
選項,可以延遲 Puppeteer 的執行。
例如,可按如下方法將速度減慢 300 毫秒:
beforeAll(async () => { browser = await puppeteer.launch({ headless: false, slowMo: 300, // slow down by 300ms }); page = await browser.newPage(); });
這樣就能更好地跟蹤測試執行情況。
使用 page.emulate
模擬裝置
使用 page.emulate()
方法可以模擬裝置指標和使用者代理。您可以自定義視口尺寸和使用者代理等屬性:
beforeAll(async () => { browser = await puppeteer.launch({ headless: false }); page = await browser.newPage(); page.emulate({ viewport: { width: 500, height: 900, }, userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36', deviceScaleFactor: 1, isMobile: false, hasTouch: false, isLandscape: false, }); });
Puppeteer 為簡化模擬提供了裝置描述符列表,可通過 puppeteer.devices
訪問。例如,模擬 iPhone 7:
const iPhone = puppeteer.devices['iPhone 7']; beforeAll(async () => { browser = await puppeteer.launch({ headless: false, slowMo: 300, // slow down by 300ms }); page = await browser.newPage(); await page.emulate(iPhone); });
使用 Jest Puppeteer
Jest Puppeteer 簡化了執行 Puppeteer 測試的配置。請按照以下步驟進行設定:
使用 yarn add -D jest-puppeteer
安裝 Jest Puppeteer。建立 jest.config.js
檔案:
module.exports = { preset: "jest-puppeteer", testRegex: "./*\\e2e\\.test\\.js$", };
建立 jest-puppeteer.config.js
檔案:
module.exports = { server: { command: "yarn start", port: 3000, launchTimeout: 10000, debug: true, }, };
Jest Puppeteer 無需在測試前構建生產網站,從而簡化了測試流程。
執行中的 Jest Puppeteer
修改測試檔案(如 e2e.test.js
)以利用 Jest Puppeteer 的功能:
// ... (existing setup) describe("App.js", () => { it("shows a success message after submitting a form", async () => { // ... (existing test logic) }); it("shows an error message if authentication fails", async () => { // ... (existing test logic) }); });
Jest Puppeteer 最大限度地減少了設定,併為表單填寫、按鈕點選和文字匹配提供了更簡潔的語法。
執行測試前,更新 App.js
中的表單,為電子郵件和密碼輸入新增名稱屬性。
<input type="email" required placeholder="Email Address" className="form-input form-input__email" onChange={(e) => { setEmail(e.target.value); }} name="emailAddress" /> <input type="password" required placeholder="Password" className="form-input form-input__password" onChange={(e) => { setPassword(e.target.value); }} name="password"
更新 .eslintrc.js
檔案,確保 ESLint 相容性:
module.exports = { env: { jest: true, }, globals: { page: true, browser: true, context: true, jestPuppeteer: true, }, };
使用 yarn test:e2e
執行測試,獲得所有測試通過的簡明輸出。
通過使用這些 Puppeteer 除錯選項和 Jest Puppeteer,你可以簡化測試流程,確保 React 應用程式的端到端測試功能強大。
小結
本文深入介紹瞭如何使用 Puppeteer 和 Jest 進行端到端測試。實際示例展示了登入表單的測試,強調了導航、元素識別和使用者操作模擬。將這些原則納入您的測試工作流程,以實現強大的 React 應用測試。
在 https://github.com/Eunit99/react-puppeteer-test 上檢視本文的資源庫
評論留言