E2E 测试框架深度对比:Playwright vs Cypress

HTMLPAGE 团队
16分钟 分钟阅读

全面对比主流 E2E 测试框架 Playwright 和 Cypress 的特性、性能和适用场景,帮助你为项目选择最合适的自动化测试工具。

#E2E测试 #Playwright #Cypress #自动化测试 #测试框架

E2E 测试的价值

端到端(E2E)测试模拟真实用户操作,验证整个应用流程是否正常工作。它是测试金字塔的顶层,虽然数量少但覆盖关键路径。

测试类型速度覆盖范围维护成本
单元测试毫秒级单个函数
集成测试秒级模块交互
E2E 测试分钟级完整流程

主流框架对比

核心特性对比

特性PlaywrightCypress
浏览器支持Chrome, Firefox, Safari, EdgeChrome, Firefox, Edge
多标签页✅ 原生支持❌ 不支持
跨域✅ 无限制⚠️ 需要配置
并行执行✅ 内置✅ 需要 Dashboard
网络拦截✅ 强大✅ 强大
移动端模拟✅ 完整⚠️ 有限
调试体验优秀

Playwright 优势

// 多浏览器测试配置
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test'

export default defineConfig({
  projects: [
    { name: 'Chrome', use: { ...devices['Desktop Chrome'] } },
    { name: 'Firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'Safari', use: { ...devices['Desktop Safari'] } },
    { name: 'Mobile', use: { ...devices['iPhone 14'] } }
  ],
  // 并行执行
  workers: process.env.CI ? 4 : undefined
})

Playwright 的多标签页支持是独特优势:

test('新标签页登录流程', async ({ context }) => {
  const page1 = await context.newPage()
  await page1.goto('/login')
  
  // 点击第三方登录,在新标签页打开
  const [newPage] = await Promise.all([
    context.waitForEvent('page'),
    page1.click('button.oauth-login')
  ])
  
  // 在新标签页完成授权
  await newPage.fill('#username', 'user')
  await newPage.click('#authorize')
  
  // 回到原页面验证登录成功
  await page1.waitForSelector('.user-profile')
})

Cypress 优势

Cypress 的交互式调试体验是其最大亮点:

// cypress/e2e/login.cy.js
describe('登录流程', () => {
  beforeEach(() => {
    cy.visit('/login')
  })
  
  it('正确的用户名密码应成功登录', () => {
    cy.get('[data-testid="username"]').type('admin')
    cy.get('[data-testid="password"]').type('password123')
    cy.get('button[type="submit"]').click()
    
    // 自动等待和重试
    cy.url().should('include', '/dashboard')
    cy.get('.welcome-message').should('contain', '欢迎回来')
  })
})

关键说明: Cypress 的命令会自动等待元素出现并自动重试,减少了显式等待代码。而 Playwright 需要更明确地处理等待逻辑。

实际场景对比

表单测试

// Playwright
test('表单提交', async ({ page }) => {
  await page.goto('/contact')
  await page.fill('input[name="email"]', 'test@example.com')
  await page.fill('textarea[name="message"]', '这是一条测试消息')
  await page.click('button[type="submit"]')
  await expect(page.locator('.success')).toBeVisible()
})
// Cypress
it('表单提交', () => {
  cy.visit('/contact')
  cy.get('input[name="email"]').type('test@example.com')
  cy.get('textarea[name="message"]').type('这是一条测试消息')
  cy.get('button[type="submit"]').click()
  cy.get('.success').should('be.visible')
})

API Mock

// Playwright - 使用 route 拦截
test('显示 mock 数据', async ({ page }) => {
  await page.route('**/api/users', route => {
    route.fulfill({
      status: 200,
      body: JSON.stringify([{ id: 1, name: '模拟用户' }])
    })
  })
  
  await page.goto('/users')
  await expect(page.locator('.user-name')).toHaveText('模拟用户')
})
// Cypress - 使用 intercept
it('显示 mock 数据', () => {
  cy.intercept('GET', '/api/users', {
    body: [{ id: 1, name: '模拟用户' }]
  }).as('getUsers')
  
  cy.visit('/users')
  cy.wait('@getUsers')
  cy.get('.user-name').should('have.text', '模拟用户')
})

文件上传

// Playwright
test('上传头像', async ({ page }) => {
  await page.goto('/profile')
  await page.setInputFiles('input[type="file"]', 'test-files/avatar.jpg')
  await expect(page.locator('.avatar-preview')).toBeVisible()
})
// Cypress
it('上传头像', () => {
  cy.visit('/profile')
  cy.get('input[type="file"]').selectFile('cypress/fixtures/avatar.jpg')
  cy.get('.avatar-preview').should('be.visible')
})

选型建议

选择 Playwright 的场景

  • 需要测试多浏览器(特别是 Safari)
  • 涉及多标签页、多窗口操作
  • 需要跨域测试
  • 已有 TypeScript 技术栈
  • 需要强大的并行能力

选择 Cypress 的场景

  • 团队刚接触 E2E 测试
  • 重视调试体验和开发者友好性
  • 项目以 Chrome 用户为主
  • 喜欢直观的 GUI 测试运行器

性能对比

在相同测试场景下:

指标PlaywrightCypress
启动时间较快较慢
单测试执行相近相近
并行测试优秀需要付费
CI 集成简单简单

迁移成本

如果从一个框架迁移到另一个:

// Cypress -> Playwright 的主要变化
// cy.visit() -> page.goto()
// cy.get() -> page.locator()
// cy.click() -> .click()
// .should('be.visible') -> await expect().toBeVisible()
// cy.intercept() -> page.route()

两个框架的 API 设计理念不同:Cypress 是链式调用,Playwright 是 async/await。迁移需要一定工作量,但核心测试逻辑可以复用。

最佳实践

  1. 从关键路径开始:优先覆盖登录、支付等核心流程
  2. 保持测试独立:每个测试应该能独立运行
  3. 使用 Page Object 模式:封装页面操作,提高可维护性
  4. 合理使用 Mock:隔离外部依赖,提高稳定性
  5. 定期维护:随着应用变化及时更新测试

无论选择哪个框架,坚持编写和维护 E2E 测试都将显著提升产品质量。