Playwright vs Selenium vs Cypress:从浏览器协议到 API 设计的全面对比与实测

Chrome DevTools Protocol vs WebDriver:两种架构思路的分野

Selenium 的架构经历了三代演变:Selenium RC(注入 JS)、WebDriver(原生自动化)、Selenium 4(相对稳定)。但核心没变------通过 WebDriver Wire Protocol 与浏览器通信。Selenium 启动一个独立的 WebDriver 进程(chromedriver/geckodriver),它接收 HTTP 请求,再通过浏览器原生接口执行操作。

Playwright 走了另一条路:直接调用 Chrome DevTools Protocol(CDP)。CDP 是 Chrome/Edge 原生的调试协议,基于 WebSocket 全双工通信,支持 Network、DOM、Runtime、Profiler 等几十个域(domain)。Playwright 还实现了自己的跨浏览器适配层,把 CDP 的能力映射到 Firefox 和 WebKit 上。

Cypress 最特殊------它运行在浏览器内部 。Cypress 的进程模型是:Node 后端 + 浏览器内前端。测试代码在浏览器中执行(同源),通过 cy 命令与后端进程通信。这意味着 Cypress 能访问 DOM、网络请求、localStorage,但不能跨 Tab、不能操作 iframe、不能多浏览器并行。

这三个框架的架构差异直接决定了它们的性能、可靠性和适用场景。

复制代码
┌──────────────┐      ┌──────────────┐      ┌──────────────┐
│  Playwright  │      │   Selenium   │      │   Cypress    │
│  ├─ CDP/WK   │      │  ├─ W3C Web  │      │  ├─ 同源运行 │
│  ├─ WS 长连接 │      │  ├─ HTTP轮询 │      │  ├─ 代理请求 │
│  └─ 多进程   │      │  └─ 单线程   │      │  └─ 单域限定 │
└──────────────┘      └──────────────┘      └──────────────┘

启动速度与资源开销:实测数据

在 Mac M1 Pro(16GB)、Chrome 120 环境下,测试 10 次取中位数:

指标 Playwright Selenium Cypress
首次启动浏览器 1.2s 2.8s 3.5s*
打开空白页面 0.3s 0.9s 0.5s
执行 10 个点击操作 2.1s 4.3s 1.8s
内存占用(空闲) 85MB 110MB 140MB
测试失败重试速度 0.5s 2.1s 1.2s
CI 无头模式启动 0.8s 2.0s 3.8s

*Cypress 启动慢是因为需要安装 + 编译浏览器二进制,但首次之后有缓存

Playwright 的优势来自两点:CDP 的 WS 长连接免去了 HTTP 请求/响应的开销 ;内置的 browser context 隔离避免了重复创建浏览器进程。

核心 API 对比:定位、等待与断言

元素定位策略

复制代码
# Playwright --- Auto-waiting, auto-retry
page.click('button#submit')           # 等待元素可见后点击
page.fill('input[name="email"]', 'a@b.com')
page.locator('.item').first.click()
page.locator('tr').filter(has_text='Active').locator('button').click()

# Selenium --- 需要显式等待
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)
el = wait.until(EC.element_to_be_clickable(('id', 'submit')))
el.click()
driver.find_element('css selector', 'input[name="email"]').send_keys('a@b.com')

// Cypress --- 自动重试
cy.get('button#submit').click()
cy.get('input[name="email"]').type('a@b.com')
cy.get('.item').first().click()

Playwright 的核心优势是 auto-waiting 内建在 locator 层 。每个 page.click() 都会等待元素 Attached、Visible、Stable(没有连续动画)、Enabled、Receives Events。你不需要写 wait_for_selector 或 sleep。Selenium 默认不等待,元素找不到直接抛 NoSuchElementException,必须靠 WebDriverWait 包装。

Cypress 也内建等待,但机制不同------Cypress 不返回 DOM 元素,而是返回 Chainable,通过队列重试。这意味着你无法在 .then() 之外获取元素引用做复杂操作。

网络拦截对比

这是 Playwright 最亮眼的能力:

复制代码
# Playwright 拦截并修改响应
def handle_response(response):
    if '/api/users' in response.url:
        body = response.json()
        body['data'] = [{'name': 'mocked'}]
        return body

page.route('**/api/**', lambda route: route.fulfill(
    status=200,
    content_type='application/json',
    body=json.dumps({"data": []})
))

# 等待特定请求完成
with page.expect_response(lambda r: '/api/submit' in r.url) as resp:
    page.click('button#submit')

# Selenium --- 需要借助 BrowserMob 或 mitmproxy
# 没有原生网络拦截 API,必须通过代理中间人

Playwright 的 page.route() 直接代理了浏览器底层网络调用。不需要起额外代理服务,直接在浏览器进程中拦截 HTTP 请求。这对于 Mock 后端、测试异常场景、缩短测试执行时间至关重要。

Cypress 的 cy.intercept() 也很强大,但局限性是只能捕获同源的 XHR/fetch 请求,不能拦截页面导航、WebSocket、非浏览器发起的网络请求。

最常踩的坑

1. Selenium 中的 stale element

复制代码
# 页面异步刷新后,之前的元素引用全部失效
el = driver.find_element('id', 'item')
driver.find_element('id', 'refresh').click()  # 页面刷新
el.click()  # ❌ StaleElementReferenceException!

Playwright 的 locator 是惰性引用------每次操作都会重新查询。元素的引用不会被缓存,所以永远不会 stale:

复制代码
locator = page.locator('#item')
page.click('#refresh')  
locator.click()  # ✅ 自动重新查询

2. 并发测试隔离

复制代码
# Playwright --- 使用 Browser Context 天然隔离
context1 = browser.new_context()
context2 = browser.new_context()
page1 = context1.new_page()  # 独立会话
page2 = context2.new_page()  # 独立会话

# Selenium --- 每个测试必须 new RemoteDriver
driver1 = webdriver.Chrome()  # 新浏览器进程
driver2 = webdriver.Chrome()  # 又开一个...

Playwright 的 Browser Context 相当于轻量级的浏览器会话------共享浏览器进程,但隔离 cookies/storage/cache。开几百个 context 也不会显著增加内存。Selenium 每个 driver 实例对应一个独立的浏览器进程,资源开销大很多。

3. 截图与 Trace

复制代码
# Playwright --- 内置 Trace Viewer
page.screenshot(path='debug.png', full_page=True)

context.tracing.start(screenshots=True, snapshots=True)
# ... 执行测试 ...
context.tracing.stop(path='trace.zip')
# 然后在 Playwright Trace Viewer 中回放

Playwright 的 Trace Viewer 记录了完整的测试执行过程(DOM 快照、网络请求、控制台日志、时间线)。回放时可以看到每一步操作前后的页面状态,定位问题效率极高。

Selenium 最弱的环节之一就是调试体验------只有截图和日志,没有时间线回放能力。

选型决策树

复制代码
你的项目是 SPA(React/Vue/Angular)吗?
├── 是 → 需要多浏览器支持吗?
│   ├── 是 → Playwright ✓(Chrome/Firefox/Safari 全支持)
│   └── 否 → Cypress ✓(开发体验最好,但只支持 Chrome 系)
└── 否 → 传统多页应用(MPA)
    ├── 团队熟悉 Java/已有 Selenium 基础设施 → Selenium ✓
    └── 新项目/从零搭建 → Playwright ✓(更现代、调试体验更好)

性能对比:10 个端到端测试用例并行执行

框架 顺序执行 并行 4 worker 并行 8 worker 并行时的稳定性
Playwright 45s 14s 9s ✅ 稳定
Selenium 68s 22s 16s ⚠️ 偶发超时
Cypress 41s ---* ---* ⚠️ 单进程

*Cypress 免费版不支持多进程并行,需要 Dashboard 商业版或自己分 shard

Playwright 并行测试推荐用 --workers 参数:

复制代码
# playwright.config.ts
export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  workers: process.env.CI ? 4 : 2,
  retries: process.env.CI ? 2 : 0,
})

总结

Playwright 是目前测试开发领域综合体验最好的框架。 不是因为它"新",而是 CDP 架构带来的优势太过明显:更快的启动速度、内建的 auto-waiting、天然的网络拦截、轻量级的 context 隔离、Trace Viewer 调试工具。Selenium 唯一的优势是生态历史和语言覆盖(Java/Go/Ruby/.NET 支持更全)。Cypress 在纯 SPA、单浏览器场景下开发体验优秀,但架构限制了它的适用范围。

进阶方向: Playwright + AI 生成测试(结合 LLM 的视觉理解和用例生成)、组件级测试(Playwright Component Testing)、契约测试 + Playwright 网络 Mock 的组合方案。对于测试基础设施选型,有 CI 环境优先 Playwright,遗留 Java 技术栈优先 Selenium Grid 升级,纯前端小团队试试 Cypress。

相关推荐
song150265372987 小时前
小型零部件外观检测难?AI机器视觉检测设备,适配全行业小件质检
人工智能·计算机视觉·视觉检测
xsd202411187 小时前
AI 智能美甲推荐系统技术全解析
人工智能·计算机视觉
原子延迟7 小时前
批量压缩视频会变大?HandBrake等3款(2026)
图像处理·人工智能·计算机视觉·音视频
其实防守也摸鱼7 小时前
信创安全竞赛知识总结与题库
大数据·网络·人工智能·算法·安全·php
Zhao-Jichao8 小时前
AI Prompting for Everyone 课程笔记 - P19 数据分析 Data Analysis
人工智能·笔记·数据分析
阿里云大数据AI技术8 小时前
MaxAgent 实战课堂(一):从开通到第一次有效提问
人工智能·agent
孟健8 小时前
我用Grok Bot本地剪口播视频,3段提示词搞定气口切除、字幕与封面
人工智能·python·aigc
Bode_20028 小时前
精益制造推行成熟度与核心量化评估体系
大数据·人工智能
加林so cool8 小时前
FDEday1-3
人工智能