起因
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)
踩过的坑
-
CI 没装 browser dependencies:
npx playwright install也要
--with-deps(Linux 系统依赖)。 -
fixture 滥用:把太多 setup 塞 fixture → test 慢。balance。
-
waitForLoadState('networkidle'):永远不到 idle(持续 polling)
→ 超时。改domcontentloaded或 specific selector。 -
trace 文件大:每 test 一个 zip 几 MB → CI 100 test 100 MB
artifact。只 retain failure trace(trace: 'retain-on-failure')。 -
headed mode 与 headless 行为差:极少数 case 一致性问题(如
focus / window size)。CI 主要 headless,dev 偶尔 headed debug。
登录后参与评论。