测试是软件开发中的必修课,有许多可行的工具来完成这项任务。本文将探讨 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 上查看本文的资源库
评论留言