Playwright vs Cypress:E2E 测试选型

起因

E2E 测试两个主流:

  • Cypress:先发优势,DX 好,2017+
  • Playwright(Microsoft,2020+):更快 / 更全 / 现代

新项目都选 Playwright,下面对比。

写法

// Playwright
import { test, expect } from '@playwright/test';

test('login', async ({ page }) => {
    await page.goto('/login');
    await page.fill('[name=email]', '[email protected]');
    await page.fill('[name=password]', 'pass');
    await page.click('button[type=submit]');
    await expect(page.locator('h1')).toHaveText('Dashboard');
});
// Cypress
describe('login', () => {
    it('logs in', () => {
        cy.visit('/login');
        cy.get('[name=email]').type('[email protected]');
        cy.get('[name=password]').type('pass');
        cy.get('button[type=submit]').click();
        cy.contains('h1', 'Dashboard');
    });
});

API 风格差异:

  • Playwright:async/await 标准 JS
  • Cypress:chained command,自定义 promise-like

Cypress chain 直观但 debug 复杂。Playwright async 更标准。

浏览器支持

Playwright Cypress
Chromium
Firefox
WebKit (Safari) ❌(实验)
Mobile emulation 部分
并发跨浏览器 部分

Playwright WebKit 支持 是杀手:Safari bug 能在 CI 测出(不能跑
真 Safari 但 WebKit engine 同一个)。

性能

我们一个项目 200 E2E 测试:

Cypress Playwright
全跑 12 min 4 min
parallel (4 worker) 5 min 1.5 min
启动 (first test) 8s 2s

Playwright 3x 快主要因为:

  • 真正并行(多 worker,单 process 多 context)
  • WebSocket 协议直连(vs Cypress iframe-based)
  • 默认 headless 优化

Cypress 并行要付费云服务 (Cypress Cloud) 或者多 CI runner。
Playwright 单进程并行免费。

auto-wait

// Playwright
await page.click('button');     // 自动等元素可点
await expect(page.locator('h1')).toHaveText('done');   // 自动 retry until match
// Cypress
cy.get('button').click();        // 自动 retry
cy.contains('h1', 'done');        // 自动 retry

两者都有 auto-wait,行为相似。
Playwright 的 expect().toBeVisible() 等 matcher 配合 retry 内置。

test isolation

Cypress:每 test 在新 iframe 跑(同 browser)。
Playwright:每 test 在新 BrowserContext(独立 cookie / storage)。

Playwright context 隔离更彻底,并行更安全。

fixture / setup

// Playwright
test.beforeEach(async ({ page }) => {
    await page.goto('/login');
});

// 共享 page state (storageState)
test.use({ storageState: 'auth.json' });
// Cypress
beforeEach(() => cy.visit('/login'));

// 共享 login
Cypress.Commands.add('login', () => { ... });
cy.login();

Playwright storageState 让"登录一次" 然后多 test 复用 session →
速度 +30%。Cypress 类似 cy.session()

debug

Playwright:

  • VS Code 扩展:UI mode 一键 step through
  • trace viewer:自动录制每步 DOM + network + screenshot
  • --debug 启动 Inspector
npx playwright test --ui          # 交互 UI
npx playwright show-trace ...     # 看失败 trace

Cypress:

  • GUI mode 一直是核心(time-travel debugger)
  • 录像 / 截图

两者 debug 体验都好,Playwright trace viewer 后来居上更详细。

codegen

npx playwright codegen example.com

打开浏览器 → 你点 / 输 → Playwright 自动生成代码。
新人写第一个测试快。

Cypress 也有 Studio 但不如 Playwright codegen 成熟。

visual regression

Playwright 内置 snapshot:

await expect(page).toHaveScreenshot('home.png');

Cypress 要装插件(cypress-image-snapshot)。

API testing

Playwright 也能测 API:

const response = await request.post('/api/users', { data: {...} });
expect(response.status()).toBe(201);

Cypress 也有 cy.request()
两者都行,API 测试不是主战场。

component testing

Cypress component testing 较早(2021+)。
Playwright 1.30+ 也有 component testing(experimental)。

实际:我们用 vitest + Testing Library 做 component test,
Playwright 只跑 full E2E。

CI 集成

# Playwright GitHub Actions
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
  if: failure()
  with:
    name: playwright-report
    path: playwright-report/

失败自动上传 HTML report + trace + screenshot → review 方便。

Cypress:

- run: npx cypress run

跟 Cypress Cloud 集成更好(recorded video / dashboard),但要付费。

flake 处理

E2E 测试 flake(不稳定)是普遍问题。
Playwright retries:

// playwright.config.ts
export default {
    retries: process.env.CI ? 2 : 0,
};

Cypress 也支持 retry。

flake 治本:

  • locator 用 stable selector(test-id 而不是 CSS class)
  • 等明确条件(不是 sleep)
  • isolate test(不依赖前一 test state)

locator best practice

// bad: brittle
page.locator('.btn-primary:nth-child(3)');

// good
page.getByRole('button', { name: 'Submit' });
page.getByTestId('submit-button');

Playwright getByRole / getByText / getByLabel 跟 RTL 一致。
accessibility-friendly + 稳定。

选择决策

  • 新项目 → Playwright(性能 + 跨浏览器 + 免费并行)
  • 老 Cypress 项目 + 测试稳 → 不必迁
  • macOS Safari 是 target → Playwright(WebKit)
  • 团队偏好 GUI debug → Cypress 仍胜

我新项目 100% Playwright。

真实迁移 case

某客户项目 Cypress 200 测试,CI 跑 15 分钟,flaky。

迁 Playwright:

  • 1 周转换 + 调优
  • CI 时间 → 4 分钟(4 worker parallel)
  • flake rate 从 5% → < 1%
  • 没付 Cypress Cloud 钱(每月 $75)

主要工作:

  • API 对应(cy.get → page.locator)
  • custom command → fixture / helper
  • 调整等待条件(用 expect with auto-retry)

踩过的坑

  1. CI 没装 browser dependenciesnpx playwright install 也要
    --with-deps(Linux 系统依赖)。

  2. fixture 滥用:把太多 setup 塞 fixture → test 慢。balance。

  3. waitForLoadState('networkidle'):永远不到 idle(持续 polling)
    → 超时。改 domcontentloaded 或 specific selector。

  4. trace 文件大:每 test 一个 zip 几 MB → CI 100 test 100 MB
    artifact。只 retain failure trace(trace: 'retain-on-failure')。

  5. headed mode 与 headless 行为差:极少数 case 一致性问题(如
    focus / window size)。CI 主要 headless,dev 偶尔 headed debug。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。