Playwright与MSW集成:构建稳定高效的前端E2E测试环境
1. 项目概述:为什么需要 Playwright 与 MSW 的组合?
如果你正在开发一个现代化的前端应用,尤其是涉及复杂用户交互和 API 调用的单页应用,那么你大概率会遇到两个核心的测试挑战:一是如何稳定、可靠地模拟用户在真实浏览器中的操作;二是如何在开发和测试阶段,不依赖不稳定的后端服务,就能模拟各种 API 响应(包括成功、失败、网络异常等)。这正是 Playwright 和 MSW 这对“黄金搭档”大显身手的地方。
Playwright 是一个强大的浏览器自动化库,由微软出品,它支持 Chromium、Firefox 和 WebKit 三大浏览器引擎。与 Selenium 或 Puppeteer 相比,它的 API 设计更现代,执行速度更快,并且原生支持自动等待、网络拦截等高级特性,让你编写端到端测试的体验流畅许多。而 MSW 则是 Mock Service Worker 的缩写,它是一个基于 Service Worker API 的请求拦截库。它的强大之处在于,它能在网络层面拦截请求,这意味着你的应用代码几乎无需修改,就能在测试、开发甚至 Storybook 中接收到你预设的模拟响应,实现了对后端 API 的“无侵入式”模拟。
将两者结合,你就能构建一个极其健壮的测试环境:用 Playwright 驱动真实浏览器,执行点击、输入、导航等用户操作;同时用 MSW 拦截这些操作触发的所有 API 请求,返回你预设的、确定性的数据。这样,你的端到端测试就完全与后端解耦了,运行速度更快,稳定性极高,可以轻松模拟出“服务器返回 500 错误”、“网络延迟 3 秒”、“返回空数据列表”等真实场景。这对于追求交付质量和开发体验的团队来说,是一个基础设施级别的提升。
接下来,我将以一个典型的 React + TypeScript 项目为例,手把手带你完成从零开始的 Playwright-MSW 项目配置,并分享我在多个项目中趟过的坑和总结的最佳实践。
2. 环境准备与项目初始化
在开始编写任何代码之前,我们需要一个干净、标准化的项目环境。这里假设你已经有了一个前端项目(比如用 Create React App、Vite 或 Next.js 初始化的),我们将在此基础上进行配置。
2.1 核心依赖安装
首先,通过 npm 或 yarn 安装 Playwright 和 MSW 的核心包。我强烈建议使用--save-dev将它们安装为开发依赖,因为测试和模拟逻辑不应该出现在生产构建中。
# 使用 npm npm install --save-dev @playwright/test playwright msw # 或使用 yarn yarn add --dev @playwright/test playwright msw这里解释一下安装的几个包:
@playwright/test: 这是 Playwright 的测试运行器,它提供了test、expect等全局函数,以及命令行工具,是我们编写和运行测试的主要入口。playwright: 这是 Playwright 的核心库,包含了浏览器操作、设备模拟等底层 API。@playwright/test运行器内部会依赖它。msw: 这是 Mock Service Worker 库本身。
安装 Playwright 核心库后,我们还需要安装它需要使用的浏览器。Playwright 提供了一个便捷的命令来自动下载 Chromium、Firefox 和 WebKit:
npx playwright install这个命令会下载浏览器二进制文件到本地缓存中。注意:首次安装可能需要一些时间,因为要下载几百兆的浏览器文件。我建议在项目初始化脚本或 CI/CD 配置中也加入此命令,确保环境一致性。
2.2 项目结构规划
一个清晰的项目结构能让后续的维护和扩展事半功倍。我推荐采用以下结构,这也是社区中比较常见的模式:
your-project/ ├── src/ │ ├── app/ # 你的应用主代码 │ ├── mocks/ # MSW 相关文件 │ │ ├── browser.ts # 用于开发环境的 MSW 处理器 │ │ ├── handlers.ts # 统一的 API 请求处理器定义 │ │ └── server.ts # 用于 Node.js 环境(测试)的 MSW 处理器 │ └── ... ├── tests/ │ ├── e2e/ # Playwright 端到端测试 │ │ ├── example.spec.ts │ │ └── auth-flow.spec.ts │ ├── fixtures/ # 测试夹具,如自定义的 `test` 扩展 │ │ └── index.ts │ └── playwright.config.ts # Playwright 配置文件 ├── package.json └── ...关键点解析:
src/mocks/: 这个目录专门存放所有 MSW 相关的逻辑。将其与应用业务代码分离,保持整洁。tests/e2e/: 所有 Playwright 端到端测试文件放在这里,通常以.spec.ts或.test.ts结尾。tests/fixtures/: 这是 Playwright 的一个高级特性,允许你自定义和扩展测试环境。我们会在这里创建一个集成了 MSW 的test对象,让每个测试用例都能自动启用模拟。playwright.config.ts: Playwright 的主配置文件,控制浏览器、设备、超时、截图等全局行为。
3. 配置 MSW:构建你的模拟服务器
MSW 的核心是“请求处理器”。你需要定义当某个特定的 API 请求被发出时,MSW 应该返回什么响应。
3.1 定义请求处理器
首先,在src/mocks/handlers.ts中定义你的处理器。这里我们模拟一个简单的用户 API。
// src/mocks/handlers.ts import { http, HttpResponse } from 'msw'; // 模拟一个用户数据 const mockUser = { id: '1', name: '测试用户', email: 'test@example.com', }; export const handlers = [ // 拦截 GET /api/user 请求,并返回模拟用户数据 http.get('/api/user', () => { console.log('MSW: 拦截到 GET /api/user 请求'); return HttpResponse.json(mockUser); }), // 拦截 POST /api/login 请求,模拟登录成功或失败 http.post('/api/login', async ({ request }) => { console.log('MSW: 拦截到 POST /api/login 请求'); const body = await request.json() as { username: string; password: string }; // 简单的模拟逻辑 if (body.username === 'admin' && body.password === 'password') { return HttpResponse.json({ token: 'fake-jwt-token' }, { status: 200 }); } else { // 模拟登录失败 return HttpResponse.json({ message: '用户名或密码错误' }, { status: 401 }); } }), // 你可以模拟网络错误或延迟 http.get('/api/slow-data', async () => { await delay(2000); // 模拟2秒网络延迟 return HttpResponse.json({ data: '延迟加载的数据' }); }), // 模拟服务器错误 http.get('/api/error', () => { return new HttpResponse(null, { status: 500 }); }), ]; // 一个简单的延迟函数 function delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); }实操心得:
- 使用
HttpResponse.json()是 MSW 推荐的方式,它比直接返回一个普通的 Response 对象更简洁。 - 在处理器函数内部使用
console.log对于调试非常有用,你可以在浏览器控制台或 Node.js 输出中看到拦截记录。 - 处理器是按定义顺序匹配的,但通常一个 RESTful API 路径只会匹配一个处理器。
3.2 配置浏览器环境(开发用)
为了让 MSW 在开发服务器(如 Vite Dev Server)中生效,我们需要在浏览器环境中启动一个 Service Worker。在src/mocks/browser.ts中设置:
// src/mocks/browser.ts import { setupWorker } from 'msw/browser'; import { handlers } from './handlers'; // 创建并导出 Worker 实例 export const worker = setupWorker(...handlers);然后,在你的应用入口文件(如src/index.tsx或src/main.tsx)中,根据环境条件启动它:
// src/main.tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; async function enableMocking() { // 仅在开发环境启用 MSW if (process.env.NODE_ENV !== 'development') { return; } const { worker } = await import('./mocks/browser'); // `worker.start()` 返回一个 Promise,确保 Service Worker 注册完成 return worker.start({ onUnhandledRequest: 'bypass', // 对于未定义的请求,直接放行到真实网络 // 安静模式,减少控制台日志 // quiet: true }); } enableMocking().then(() => { ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <App /> </React.StrictMode> ); });重要提示:onUnhandledRequest选项非常关键。设置为'bypass'意味着 MSW 不会处理那些你没有在handlers中定义的请求(比如对静态资源、字体或你暂时不想模拟的第三方 API 的请求),它们会正常发出。这避免了开发时因为模拟不完整而导致页面功能缺失。你也可以设置为'warn'来在控制台看到警告,帮助你查漏补缺。
3.3 配置 Node.js 环境(测试用)
Playwright 测试运行在 Node.js 环境中,因此我们需要一个适用于 Node 的 MSW 设置。在src/mocks/server.ts中创建:
// src/mocks/server.ts import { setupServer } from 'msw/node'; import { handlers } from './handlers'; // 创建并导出 Server 实例,用于 Node 环境(如测试、SSR) export const server = setupServer(...handlers);这个server实例将在我们的 Playwright 测试夹具中被启动和关闭。
4. 集成 Playwright:创建支持 MSW 的测试环境
现在来到了最核心的部分:如何让 Playwright 测试在启动浏览器时,自动加载我们的 MSW 模拟。
4.1 创建自定义测试夹具
Playwright Test 提供了test.extend功能,允许你创建自定义的、可重用的测试环境。我们在tests/fixtures/index.ts中创建一个集成了 MSW 的test对象。
// tests/fixtures/index.ts import { test as baseTest } from '@playwright/test'; import { server } from '../../src/mocks/server'; // 扩展基础的 `test` fixture export const test = baseTest.extend<{ enableMocking: void; }>({ // 这个 fixture 会在每个测试用例之前执行 enableMocking: [async ({ page }, use) => { // 1. 在测试服务器启动前,启动 MSW Server server.listen({ onUnhandledRequest: 'error' }); // 测试中,未处理的请求应报错 // 2. 将 MSW 的请求处理器注入到页面上下文中 await page.addInitScript(() => { // 这里是一个关键技巧:我们通过 `window.__msw` 将启动函数暴露给页面 // 实际的启动逻辑在下一步的 `route` 中处理 window.__msw = { worker: null }; }); // 3. 拦截页面初始的 HTML 请求,在 `<head>` 中注入 MSW Service Worker 注册脚本 await page.route('**/*', async (route, request) => { // 只处理文档类型的请求(即 HTML 页面) if (request.resourceType() === 'document') { const response = await route.fetch(); const body = await response.text(); // 在 </head> 标签前注入我们的脚本 const injectedBody = body.replace( '</head>', `<script> (async () => { // 动态导入 msw/browser 和 handlers,避免在非测试环境打包 const { setupWorker } = await import('https://unpkg.com/msw@latest/browser'); const { handlers } = await import('${process.env.NODE_ENV === 'production' ? '/handlers.js' : '/src/mocks/handlers.ts?inject'}'); // 路径需根据你的构建工具调整 const worker = setupWorker(...handlers); window.__msw.worker = worker; await worker.start({ onUnhandledRequest: 'bypass', serviceWorker: { url: '/mockServiceWorker.js', // MSW 会自动生成这个文件 }, }); console.log('MSW worker started in Playwright.'); })(); </script> </head>` ); await route.fulfill({ response, body: injectedBody, headers: { ...response.headers(), 'content-type': 'text/html' }, }); } else { // 非文档请求,直接继续 route.continue(); } }); await use(); // 执行测试用例 // 4. 测试用例结束后,关闭 MSW Server server.close(); }, { auto: true }], // `auto: true` 表示这个 fixture 会自动为每个使用它的测试用例运行 }); export { expect } from '@playwright/test'; // 重新导出 expect深度解析与避坑指南:
server.listen()与server.close():这是 MSW Node Server 的生命周期管理。listen()必须在任何可能发出 API 请求的代码之前调用,close()则在测试后清理。onUnhandledRequest: 'error'在测试中非常有用,它能立即暴露你遗漏模拟的 API 调用,让测试更加严格。page.addInitScript:这个方法在页面加载任何框架或脚本之前,向页面上下文中注入 JavaScript。我们在这里初始化一个全局变量window.__msw作为占位符。page.route与 HTML 注入:这是整个集成中最巧妙也最复杂的一步。我们拦截所有请求,但只对 HTML 文档请求进行处理。通过修改响应体,我们在页面的<head>中动态注入了一段脚本。这段脚本会:- 动态导入 MSW 的浏览器库和你的处理器(这里使用了 CDN,实际项目中你可能需要根据构建工具调整路径,例如使用相对路径或通过
fs模块读取文件内容后内联)。 - 启动 MSW Worker。
- 关键点:为什么不在
addInitScript里直接启动?因为addInitScript执行时,页面可能还没有加载所需的模块系统(如 ES Modules)。通过route注入,可以确保脚本在页面上下文中以正确的方式加载和执行。
- 动态导入 MSW 的浏览器库和你的处理器(这里使用了 CDN,实际项目中你可能需要根据构建工具调整路径,例如使用相对路径或通过
路径问题:示例中
import的路径 ('/src/mocks/handlers.ts?inject') 是一个示意。在实际的 Vite 项目中,你可以利用 Vite 的原始文件导入。在 Webpack 或其它构建工具中,你可能需要将 handlers 编译成一个独立的文件并服务。这是最常见的配置难点,需要根据你的构建工具进行调整。Service Worker 文件:MSW 在启动时,会尝试注册一个名为
mockServiceWorker.js的 Service Worker 文件。你需要确保这个文件在服务器的根路径 (/) 下可访问。在开发服务器中,MSW 的worker.start()通常会帮你处理。但在 Playwright 的测试服务器中,你可能需要手动将其复制到静态资源目录,或者在配置中指定其路径。
4.2 配置 Playwright
接下来,配置playwright.config.ts来使用我们的自定义夹具,并设置一些常用的选项。
// tests/playwright.config.ts import { defineConfig, devices } from '@playwright/test'; import path from 'path'; // 导入我们自定义的 test fixture import { test } from './fixtures'; export default defineConfig({ // 使用我们扩展后的 test,这样所有测试文件都能自动获得 MSW 支持 test, // 全局的 expect 也从 fixtures 中导入 expect, // 测试文件的位置 testDir: './e2e', // 匹配测试文件 testMatch: '**/*.spec.ts', // 是否并行运行测试。在集成 MSW 初期,建议关闭并行以排查问题 fullyParallel: false, // 每个测试文件的并行工作进程数 workers: 1, // 测试报告 reporter: [ ['html', { outputFolder: 'playwright-report' }], ['list'], // 在控制台输出简洁结果 ], // 项目配置:可以定义不同的浏览器或设备进行测试 projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, // 可以取消注释以添加更多浏览器测试 // { // name: 'firefox', // use: { ...devices['Desktop Firefox'] }, // }, // { // name: 'webkit', // use: { ...devices['Desktop Safari'] }, // }, ], // Web 服务器配置:在运行测试前启动你的开发服务器 webServer: { command: 'npm run dev', // 你的开发启动命令,如 `vite dev` 或 `react-scripts start` url: 'http://localhost:5173', // 你的开发服务器地址 reuseExistingServer: !process.env.CI, // 在非 CI 环境下重用已存在的服务器 timeout: 120 * 1000, // 启动超时时间 stdout: 'pipe', // 将服务器日志输出到管道,便于调试 stderr: 'pipe', }, // 全局超时设置 timeout: 30 * 1000, // 每个测试的最大超时时间 expect: { timeout: 10 * 1000, // expect 断言的最大等待时间 }, });配置要点:
webServer: 这个配置让 Playwright 在运行测试前自动启动你的前端开发服务器。确保command和url与你的项目匹配。reuseExistingServer在本地开发时非常方便,避免重复启动服务器。workers: 1: 在集成 MSW 的初期,强烈建议将workers设为 1(禁用并行)。因为 MSW Server (server.listen()) 是全局单例的,并行测试可能会导致请求处理混乱。待集成稳定后,可以尝试开启并行,但需要更仔细地管理测试隔离。timeout: 根据你的应用和网络情况调整超时时间。如果测试涉及大量 MSW 模拟延迟,可能需要适当延长。
5. 编写第一个端到端测试
环境配置妥当后,我们来编写一个实际的测试用例。假设我们有一个简单的登录页面。
// tests/e2e/auth.spec.ts import { test, expect } from '../fixtures'; // 导入我们自定义的 test test.describe('用户认证流程', () => { test.beforeEach(async ({ page }) => { // 每个测试前导航到登录页 await page.goto('/login'); }); test('使用正确凭据应成功登录', async ({ page }) => { // 1. 断言页面加载正确 await expect(page.getByRole('heading', { name: /登录/i })).toBeVisible(); // 2. 填写表单 await page.getByLabel('用户名').fill('admin'); await page.getByLabel('密码').fill('password'); // 3. 点击登录按钮 await page.getByRole('button', { name: /登录/i }).click(); // 4. 验证登录成功后的行为 // 假设成功后会跳转到首页,并显示用户名 await expect(page).toHaveURL('/dashboard'); await expect(page.getByText('欢迎,admin')).toBeVisible(); // 5. (可选) 验证是否发出了特定的 API 请求 // 这需要更高级的 MSW 和 Playwright 网络监听配合,后续可以扩展 }); test('使用错误凭据应显示错误信息', async ({ page }) => { await page.getByLabel('用户名').fill('wronguser'); await page.getByLabel('密码').fill('wrongpass'); await page.getByRole('button', { name: /登录/i }).click(); // 验证页面上出现了错误提示 await expect(page.getByText('用户名或密码错误')).toBeVisible(); // 验证页面没有跳转 await expect(page).toHaveURL('/login'); }); test('登录 API 模拟网络延迟', async ({ page }) => { // 我们可以通过修改 MSW handler 来动态改变响应吗? // 一种方法是在测试内部,通过 `page.evaluate` 与注入的 `window.__msw.worker` 通信, // 动态重置 handlers。但这比较复杂。 // 更简单的做法是:为这个特定的测试场景,在 `handlers.ts` 中定义一个专门的慢速接口, // 然后在这个测试中访问那个接口。 await page.goto('/slow-data-page'); // 假设有这个页面 const loadingIndicator = page.getByText('加载中...'); await expect(loadingIndicator).toBeVisible(); // 等待加载完成,这个等待时间应该大于我们模拟的延迟(2秒) await expect(loadingIndicator).not.toBeVisible({ timeout: 3000 }); await expect(page.getByText('延迟加载的数据')).toBeVisible(); }); });编写测试的最佳实践:
- 使用定位器最佳实践:优先使用
page.getByRole()、page.getByText()、page.getByLabel()等语义化定位器,它们比基于 CSS 选择器的page.locator(‘.btn’)更稳定、可读性更好。 - 善用断言:Playwright 的
expect是异步的,并且内置了自动等待机制。像toBeVisible()会一直等到元素出现在 DOM 中并且可见。 - 测试隔离:每个测试都应该独立运行,不依赖其他测试的状态。
beforeEach钩子是用来重置状态到已知起点的好地方。 - 模拟边界情况:利用 MSW,你可以轻松测试成功、失败、延迟、超时、空数据等各种场景,这是传统依赖真实后端的 E2E 测试难以做到的。
6. 运行测试与调试技巧
配置和编写完成后,让我们运行测试并看看如何调试可能出现的问题。
6.1 运行测试
在package.json中添加脚本:
{ "scripts": { "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui", // 使用炫酷的 UI 模式 "test:e2e:debug": "playwright test --debug", // 调试模式 "test:e2e:chromium": "playwright test --project=chromium" // 只运行 Chrome 测试 } }然后在终端运行:
npm run test:e2e6.2 常见问题与排查技巧
即使按照教程配置,你也可能会遇到一些问题。以下是我在实践中总结的常见“坑”及其解决方案:
问题1:MSW 在 Playwright 中没有生效,请求还是发到了真实后端。
- 检查点1:Service Worker 是否注册成功。在测试中,你可以在
page.route注入的脚本里加入更详细的console.log,或者在测试中使用page.on(‘console’)监听浏览器日志。也可以打开 Playwright 的追踪功能,查看网络请求是否被(MSW)标记。 - 检查点2:
mockServiceWorker.js文件是否可访问。在浏览器中打开开发者工具的 “Application” -> “Service Workers” 面板,查看是否注册成功。如果失败,检查 Playwright 启动的服务器是否能在根路径 (/) 提供这个文件。你可能需要配置静态文件服务。 - 检查点3:处理器路径是否正确。动态导入
handlers的路径是最大的难点。一个调试方法是:先不要在page.route中动态导入,而是直接将 handlers 数组硬编码在注入的脚本里,看是否能工作。如果能,问题就是路径解析不对。
问题2:测试并行运行时出现随机失败。
- 解决方案:如前所述,先设置
workers: 1关闭并行。如果问题消失,说明是测试隔离问题。确保每个测试不共享任何全局状态(如server实例是全局的,但它的 handlers 可以被重置)。考虑在beforeEach中使用server.resetHandlers()来为每个测试重置模拟行为。
问题3:TypeScript 报错,找不到window.__msw的类型定义。
- 解决方案:在全局类型声明文件中添加定义。创建
src/global.d.ts:// src/global.d.ts export {}; declare global { interface Window { __msw: { worker: import('msw/browser').SetupWorkerApi | null; }; } }
问题4:Playwright 测试报告page.goto超时。
- 检查点1:Web 服务器是否成功启动。检查
playwright.config.ts中的webServer配置,url是否正确,服务器启动命令 (command) 是否能在测试环境下正常运行(有些启动命令依赖环境变量)。 - 检查点2:MSW 注入脚本是否阻塞了页面加载?检查注入的脚本是否有语法错误,或者动态导入失败导致页面卡死。可以在注入脚本外围加上
try-catch并输出错误到控制台。 - 检查点3:临时注释掉
page.route和 MSW 相关的所有代码,看页面是否能正常加载。如果能,问题就出在 MSW 集成部分。
问题5:如何调试 Playwright 执行过程?
- UI 模式:使用
npm run test:e2e:ui启动 Playwright 的图形化界面。你可以看到每个测试步骤,实时查看浏览器,设置断点,非常直观。 - Debug 模式:使用
npm run test:e2e:debug。它会在第一个测试处暂停,并打开一个浏览器开发者工具,你可以像调试普通网页一样调试测试脚本。 - 追踪:在
playwright.config.ts中启用追踪use: { trace: ‘on-first-retry’ },测试失败时会生成一个追踪文件,可以用playwright show-trace命令打开,查看详细的网络请求、DOM 快照和时间线。
7. 高级技巧与项目优化
当基础功能跑通后,可以考虑以下优化来提升测试套件的可维护性和威力。
7.1 动态修改 Mock 响应
有时,你希望在一个测试套件或单个测试中,临时改变某个 API 的响应。MSW 的server实例提供了相应的方法。
// 在测试文件中 import { server } from '../../src/mocks/server'; import { http, HttpResponse } from 'msw'; test.describe('用户仪表盘', () => { test.beforeEach(() => { // 在每个测试前重置 handlers 到初始状态,避免测试间污染 server.resetHandlers(); }); test('当用户没有项目时显示空状态', async ({ page }) => { // 动态覆盖 `/api/projects` 的 handler,返回空数组 server.use( http.get('/api/projects', () => { return HttpResponse.json([]); }) ); await page.goto('/dashboard'); await expect(page.getByText('你还没有创建任何项目')).toBeVisible(); }); test('当加载项目失败时显示错误', async ({ page }) => { // 动态覆盖 handler,模拟服务器错误 server.use( http.get('/api/projects', () => { return new HttpResponse(null, { status: 500 }); }) ); await page.goto('/dashboard'); await expect(page.getByText('加载失败,请重试')).toBeVisible(); }); });server.use()是极其强大的功能,它允许你在不修改原始handlers.ts文件的情况下,为特定测试场景定制行为。
7.2 配合请求检查进行断言
你不仅可以模拟响应,还可以断言是否发出了正确的请求。这需要结合 Playwright 的网络监听。
test('创建项目时应发送正确的 POST 请求', async ({ page }) => { // 创建一个数组来捕获请求 const capturedRequests: any[] = []; // 监听特定的网络请求 page.on('request', (request) => { if (request.url().includes('/api/projects') && request.method() === 'POST') { capturedRequests.push(request.postDataJSON()); } }); // 或者,使用 Playwright 更简洁的 API(需要确保请求未被 MSW 完全“吞噬”,因为 MSW 拦截后,请求可能不会到达网络层) // 更可靠的方式是直接监听 MSW 的 handler 被调用,这需要你在 handler 里暴露状态或使用其他事件机制。 await page.goto('/projects/new'); await page.getByLabel('项目名称').fill('我的新项目'); await page.getByRole('button', { name: /创建/i }).click(); // 等待请求发出(假设成功后会跳转或显示提示) await expect(page.getByText('项目创建成功')).toBeVisible(); // 断言捕获到的请求 expect(capturedRequests).toHaveLength(1); expect(capturedRequests[0]).toEqual({ name: '我的新项目' }); });注意:由于 MSW 在 Service Worker 层拦截请求,Playwright 的page.on(‘request’)可能监听不到被 MSW 处理的请求。一个变通方法是,在 MSW 的 handler 里将请求详情记录到一个全局变量,然后通过page.evaluate()在测试中读取。
7.3 在 CI/CD 流水线中运行
在持续集成环境中,你需要确保环境的一致性。
- 安装依赖和浏览器:在 CI 脚本中,确保在安装 npm 依赖后运行
npx playwright install --with-deps。--with-deps会同时安装浏览器所需的系统依赖(这在 Linux CI 环境中尤其重要)。 - 无头模式:Playwright 默认以无头模式运行,适合 CI。
- 报告:配置
reporter为‘github’或‘junit’以生成 CI 系统能识别的报告。 - 处理静态文件:确保你的 CI 环境能正确提供
mockServiceWorker.js文件。你可能需要在构建步骤中,将node_modules/msw/public/mockServiceWorker.js复制到你的静态资源输出目录。
一个简单的 GitHub Actions 配置示例:
name: Playwright E2E Tests on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '18' - name: Install dependencies run: npm ci - name: Install Playwright Browsers run: npx playwright install --with-deps chromium - name: Build your app (if needed) run: npm run build - name: Run Playwright tests run: npm run test:e2e env: CI: true - uses: actions/upload-artifact@v4 if: always() with: name: playwright-report path: playwright-report/ retention-days: 30配置完成后,你的团队就能在每次代码提交时,获得一份完全独立、稳定且快速的端到端测试报告,这将是保障前端应用质量的一道坚实防线。从手动点击测试到自动化验证的转变,带来的效率和信心提升是巨大的。
