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_selectorsleep。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。

相关推荐
Ai_easygo5 小时前
AI Agent开发入门——从ReAct到Tool Calling,拆解Agent的底层运行逻辑
前端·人工智能·react.js
IT_陈寒6 小时前
Redis的持久化配置把我坑惨了:你以为数据安全了?
前端·人工智能·后端
miaowu3576 小时前
AI智能体推动数字化转型:从流程自动化到决策辅助的完整路线图
大数据·人工智能·自动化
阿里云大数据AI技术6 小时前
阿里云 Elasticsearch 日志采集与加工服务:让日志链路少一串组件,多一份稳定
人工智能·elasticsearch
ksueh6 小时前
AI写小说长篇创作中的上下文局限与外部记忆系统实践
人工智能
互联网江湖6 小时前
珞石机器人:向左科技股,向右零部件制造商?
大数据·人工智能
阿里云大数据AI技术7 小时前
从数据湖到多模态湖仓-基于阿里云EMR Serverless StarRocks与DLF Paimon构建AI时代的统一分析检索架构
人工智能
前端的阶梯7 小时前
浅谈Workflow和Agent的区别
人工智能
正在走向自律7 小时前
Deepseek V4 Flash 高效应用实战指南
人工智能·deepseek·deepseek v4·ai赋能中心