前端 E2E 测试(End-to-End Test,全流程端到端测试)指的是在真实用户使用的场景下,模拟用户在浏览器里对前端应用的操作,从而验证整个系统是否能按照预期工作。
与单元测试、集成测试的区别在于:
- 单元测试(Unit Test):测试一个函数/组件是否正确工作。
- 集成测试(Integration Test):测试几个模块组合在一起是否能正常运行。
- E2E 测试:模拟一个真实用户从打开页面、点击按钮、输入内容、跳转页面,到后台 API 返回数据、页面渲染结果的全流程。
特点
- 运行环境接近真实用户体验
一般通过真实浏览器(Chrome/Firefox)或无头浏览器(Headless Chrome)运行,模拟点击、输入、滚动、截图等操作。
- 跨层验证
不仅验证前端页面,还会通过调用后端 API、数据库的响应来确保整个系统的链路没问题。
- 面向用户行为
关注的是 用户能不能完成一件事情,而不是内部代码实现。
常用工具
- Cypress:现在最流行的 E2E 测试框架之一,支持实时调试、快照、断言直观。
- Playwright:微软推出,支持多浏览器(Chromium、Firefox、WebKit),功能强大。
- Puppeteer:Google 出品,控制 Chromium 的无头浏览器工具,偏向于自动化+测试。
- Selenium:老牌 E2E 测试框架,支持多语言、多浏览器。
Playwright 基础
一、安装与初始化
# 在前端项目根目录
pnpm add -D @playwright/test
# 一键装浏览器
npx playwright install
# 可选:生成示例结构
npx playwright codegen https://www.summer889.com
推荐目录(monorepo 也通用):
.
├─ playwright.config.ts
├─ tests/
│ ├─ smoke/ # 冒烟用例
│ ├─ auth/ # 登录相关
│ ├─ features/ # 业务场景
│ └─ fixtures/ # 共享夹具
└─ e2e-assets/ # 测试数据、上传文件、下载落地等
package.json 脚本:
{
"scripts": {
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:headed": "playwright test --headed",
"test:e2e:debug": "PWDEBUG=1 playwright test",
"test:e2e:report": "playwright show-report"
}
}
二、基础配置(含 Vite 预览、一键起停)
playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
expect: { timeout: 5_000 },
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
reporter: [['html', { open: 'never' }], ['list']],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:5173',
trace: 'on-first-retry', // 失败重试时录制 trace
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 10_000,
navigationTimeout: 15_000,
},
// 让 Playwright 帮你启/停本地 Vite 预览(或 Next/Express)
webServer: {
command: 'pnpm run preview', // 例如 vite preview
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
// 可选移动端视口
// { name: 'Mobile Chrome', use: { ...devices['Pixel 7'] } },
],
});
如果
Vite
:确保有 pnpm run preview 脚本(vite preview)
三、选择器与可测性约定
建议用 data-testid
或 data-test
做稳定选择器,避免 class/文本波动:
<button data-testid="login-submit">登录</button>
<input data-testid="email" />
Playwright 用法:
await page.getByTestId('email').fill('user@163.com');
await page.getByTestId('login-submit').click();
四、用例:
登录(存储态复用)
- 首次登录并保存
storageState
:
tests/auth/login.setup.spec.ts
import { test, expect } from '@playwright/test';
test('setup: login and save state', async ({ page }) => {
await page.goto('/login');
await page.getByTestId('email').fill('e2e@demo.com');
await page.getByTestId('password').fill('P@ssw0rd!');
await page.getByTestId('login-submit').click();
await expect(page).toHaveURL(/dashboard/);
// 保存登录态,后续用例复用,避免每次都走登录流程
await page.context().storageState({ path: 'tests/fixtures/authState.json' });
});
- 在业务用例里复用登录态:
playwright.config.ts
增加一个带 storageState
的 project 或在用例中指定:
// 在某个项目里开启
{
name: 'chromium-auth',
use: {
...devices['Desktop Chrome'],
storageState: 'tests/fixtures/authState.json'
}
}
或在单测里:
test.use({ storageState: 'tests/fixtures/authState.json' });
test('已登录访问订单页', async ({ page }) => {
await page.goto('/orders');
await page.getByRole('heading', { name: '我的订单' }).isVisible();
});
网络拦截
test('搜索接口 mock', async ({ page }) => {
await page.route('**/api/search**', async route => {
const url = route.request().url();
if (url.includes('q=手机')) {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: [{ id: 1, name: 'Playwright Phone' }] })
});
}
return route.continue();
});
await page.goto('/');
await page.getByPlaceholder('搜索').fill('手机');
await page.keyboard.press('Enter');
await expect(page.getByText('Playwright Phone')).toBeVisible();
});
Electron 应用
Playwright 支持 Electron:
import { _electron as electron, test, expect } from '@playwright/test';
test('electron smoke', async () => {
const app = await electron.launch({ args: ['.'] }); // 你的 electron 主进程入口
const win = await app.firstWindow();
await win.waitForLoadState('domcontentloaded');
await expect(win.getByText('欢迎')).toBeVisible();
await app.close();
});
若前端由 Vite 提供页面,仍可用 webServer 起预览,再由 Electron 加载;E2E 可分两层:
Web(浏览器)
和Electron(壳)
。
五、测试报告
用 Playwright 生成 / 查看测试报告主要有 3 种常用方式:HTML 报告、JUnit/JSON 等机器可读报告、以及 Trace/Screenshot/Video 附件。
- HTML 报告(最常用)
配置(推荐放在 playwright.config.ts
)
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: [
['html', { open: 'never', outputFolder: 'playwright-report' }],
// 可选:还想要控制台 list 报告
['list']
],
use: {
trace: 'on-first-retry', // 失败重试时会录制 trace
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
});
运行生成报告
pnpm test:e2e # 或 npx playwright test
本地查看报告
pnpm test:e2e:report # 等价于 npx playwright show-report
# 或指定目录:
npx playwright show-report playwright-report
打开的是一个可交互的网页:能按用例筛选、看重试历史、点进单测查看步骤与附件(截图/视频/trace)。
一键脚本(建议加到 package.json)
{
"scripts": {
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:report": "playwright show-report",
"test:e2e:trace": "playwright show-trace test-results/**/trace.zip"
}
}