单元测试能保证单个组件或函数的正确性,但真实的应用是由多个模块协同工作的。当组件相互组合、路由跳转、API 数据真实返回时,集成测试和端到端(E2E)测试就变得不可或缺。它们从用户的角度出发,验证完整的业务流程是否正常运行。
集成测试 vs E2E 测试
- 集成测试:验证几个模块组合在一起是否正常工作。在 React 中,通常表现为测试一个页面级组件,模拟 API 返回、用户点击、状态变化,但不会启动真实的后端服务或完整浏览器环境。仍可以使用 Jest + React Testing Library,只是测试范围更大。
- E2E 测试:完全模拟真实用户操作,启动真实浏览器(或 headless 模式),访问完整应用(包括前端、后端、数据库),验证从打开页面到完成一笔交易的整个流程。E2E 测试最接近用户真实体验,但运行慢、维护成本高。
良好的测试金字塔告诉我们:单元测试多、集成测试适中、E2E 测试少。Cypress 和 Playwright 是当前最主流的 E2E 测试框架,它们也可以用来编写集成测试(配合 mock 后端)。
Cypress:开发者友好的 E2E 测试框架
Cypress 是一个基于 JavaScript 的端到端测试工具,专为现代 Web 应用设计。它的核心特色是运行在浏览器内,可以直接访问 DOM、网络请求、本地存储等,调试体验极佳。
核心能力:
- 时间旅行:测试运行时会自动截图,你可以回放每一步操作时的界面状态。
- 自动等待:查询元素时自动重试,无需写大量
sleep或wait。 - 网络请求控制:支持拦截和修改 API 响应(
cy.intercept),方便模拟各种接口场景。 - 实时重载:改动测试代码后自动重新运行,开发体验接近前端热更新。
- 调试直观:在 Cypress 的电子界面里可以看到浏览器控制台、网络日志、DOM 快照。
安装与基础使用:
npm install cypress --save-dev
npx cypress open
第一次运行会生成 cypress 目录,包含配置文件和示例测试。一个典型的 React 应用登录测试如下:
// cypress/e2e/login.cy.js
describe('登录流程', () => {
it('应该成功登录并跳转到主页', () => {
cy.visit('http://localhost:3000/login');
cy.get('input[name="username"]').type('testuser');
cy.get('input[name="password"]').type('password123');
cy.get('button[type="submit"]').click();
// 断言跳转到主页
cy.url().should('include', '/dashboard');
cy.contains('欢迎回来,testuser').should('be.visible');
});
it('登录失败时应显示错误提示', () => {
cy.intercept('POST', '/api/login', {
statusCode: 401,
body: { message: '用户名或密码错误' }
}).as('loginAttempt');
cy.visit('/login');
cy.get('input[name="username"]').type('wrong');
cy.get('input[name="password"]').type('wrong');
cy.get('button[type="submit"]').click();
cy.wait('@loginAttempt');
cy.contains('用户名或密码错误').should('exist');
});
});
实用技巧:
- 使用
cy.intercept模拟 API 响应,可模拟正常、延迟、失败等场景,无需启动真实后端。 - 将登录等通用前置操作封装为自定义命令
Cypress.Commands.add('login', () => {...}),减少重复代码。 - 对于需要多标签页或跨域的场景有所限制(Cypress 单标签、同源),复杂场景可考虑 Playwright。
Playwright:微软出品的跨浏览器自动化利器
Playwright 是一个由微软开发的端到端测试框架,支持 Chromium、Firefox、WebKit 三种浏览器引擎。你可以用同一套脚本在多种浏览器上并行测试,覆盖 Safari 等 webkit 内核浏览器,这是 Cypress 目前不直接支持的。
核心能力:
- 跨浏览器支持:一份代码跑在 Chrome、Edge、Firefox、Safari。
- 自动等待:和 Cypress 类似的智能等待机制,避免 flaky 测试。
- 网络拦截与控制:可修改请求/响应,模拟网络异常、文件上传下载等。
- 强大的选择器引擎:支持文本选择器、角色选择器、CSS/XPath,并能自动生成选择器。
- 多标签页、多窗口、iframe 支持:天生支持复杂场景,如第三方登录窗口。
- 测试生成器:
npx playwright codegen可录制操作生成测试代码,快速上手。 - Trace Viewer:记录完整的测试运行过程,包含 DOM 快照、网络请求、控制台日志,用于事后分析失败原因。
安装与基础使用:
npm init playwright@latest
该命令会引导你安装浏览器驱动、创建配置文件。一个完全相同的登录测试用 Playwright 写出来是这样的:
// tests/login.spec.js
const { test, expect } = require('@playwright/test');
test.describe('登录流程', () => {
test('应该成功登录并跳转到主页', async ({ page }) => {
await page.goto('http://localhost:3000/login');
await page.fill('input[name="username"]', 'testuser');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
// 等待导航完成
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.locator('text=欢迎回来,testuser')).toBeVisible();
});
test('登录失败时应显示错误提示', async ({ page }) => {
// 拦截登录请求并返回失败
await page.route('**/api/login', route => {
route.fulfill({
status: 401,
body: JSON.stringify({ message: '用户名或密码错误' })
});
});
await page.goto('/login');
await page.fill('input[name="username"]', 'wrong');
await page.fill('input[name="password"]', 'wrong');
await page.click('button[type="submit"]');
await expect(page.locator('text=用户名或密码错误')).toBeVisible();
});
});
实用技巧:
- 使用
page.route进行网络拦截,比 Cypressintercept语法稍复杂但功能更强大。 - 利用
test.use({ storageState: 'auth.json' })可以保存和复用登录态,避免每次测试都登录。 - 在多浏览器上并行测试:
npx playwright test --browser=all。 - 生成可视化报告:
npx playwright show-report,查看 Trace 分析失败原因。
Cypress vs Playwright:选型对比
| 特性 | Cypress | Playwright |
|------|--------|------------|
| 浏览器支持 | Chrome、Firefox、Edge(Chromium内核) | Chrome、Firefox、Safari(WebKit)全兼容 |
| 调试体验 | 最强,时间旅行、实时热重启 | 较好,Trace Viewer 功能强大 |
| 多标签/窗口 | 不支持 | 原生支持 |
| 多浏览器并行 | 通过插件或付费仪表板 | 内置支持 |
| 网络拦截 | cy.intercept 简洁直观 | page.route 更底层灵活 |
| 社区生态 | 非常成熟,插件丰富 | 快速增长,微软背书 |
| 性能 | 中等(串行执行) | 更快,原生并行 |
| 学习曲线 | 低,API 语义化 | 中等,Playwright 需要理解异步等待 |
建议:
- 如果你的团队已经使用 Cypress,且不需要跨浏览器测试或复杂多标签页场景,Cypress 的开发体验仍然是一流的。
- 新项目、需要覆盖 Safari 或其他浏览器、或者需要多标签页的场景,优先选择 Playwright。
- 也可以两者共用:Cypress 用于开发阶段的快速反馈,Playwright 用于 CI/CD 中进行多浏览器验收测试。
测试的实用原则
- 从用户路径出发:不要测试代码实现的细节,而是模拟真实操作步骤。
- 尽量使用稳定的选择器:优先使用
data-testid属性而不是 CSS class 或文本,避免 UI 调整导致测试失效。 - 控制测试环境:通过环境变量或 fixture 生成一致的测试数据,避免依赖外部不稳定服务。
- 只测试关键流程:E2E 测试成本高,聚焦核心业务路径(注册、登录、下单、支付),边缘情况留给单元测试和集成测试。
- CI 集成:在 GitHub Actions / GitLab CI 等流水线中运行 E2E 测试,保证合并前的质量门禁。
# GitHub Actions 中使用 Playwright 示例
- name: Run E2E tests
uses: microsoft/playwright-github-action@v1
with:
command: npx playwright test
结合 React 项目的实战建议
- 集成测试:在 React 中可以使用 React Testing Library 渲染整个页面组件,并 mock 掉 API 请求(例如用 MSW),这属于集成测试范畴,运行速度快,可以验证 UI 与数据流的连接。
- E2E 测试:启动开发服务器(
npm run dev)后,用 Cypress 或 Playwright 访问该 URL。通常会在 CI 中先构建生产版本,然后启动静态服务器运行测试。
无论选择哪个工具,将 E2E 测试加入到你的开发流程中,都能大幅降低回归风险,让你有信心频繁部署。它们是质量保障的最后一道防线,让“上线不慌”成为现实。