在 Web 自动化测试领域,Selenium 长期是行业标准,但稳定性差、等待机制笨拙、脚本维护成本高,一直是测试工程师的日常痛点。Playwright 凭借直连浏览器内核的架构异军突起,而 Cypress 走了一条完全不同的路------它直接跑在浏览器内部,与被测应用共享同一个运行循环(run loop),带来了前所未有的调试体验和执行稳定性。
今天我们从零拆解 Cypress 的工作原理、双进程架构、命令队列调度、常用核心方法、与 Selenium/Playwright 的性能对比、完整优劣势,帮你彻底吃透这款"开发者体验最好"的自动化测试框架。
01 Cypress 完整工作原理(结合架构图)

Cypress 最核心的设计决策是:测试代码运行在浏览器内部,而不是浏览器外部。
Selenium 的链路是 测试脚本 → HTTP 请求 → WebDriver 驱动 → 浏览器,每一步都是跨进程远程命令。Cypress 完全反其道而行------它把测试脚本注入浏览器,在浏览器里用 iframe 承载被测应用,测试代码和应用代码跑在同一个浏览器进程中。
整体架构
scss
┌─────────────────────────────────────────────────────┐
│ 浏览器(Chrome/Firefox/Edge) │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ Cypress Test Runner │ │ 被测应用(iframe) │ │
│ │ iframe │ │ │ │
│ │ │ │ ┌──────────────┐ │ │
│ │ • 测试代码执行 │◄─┼──►│ window │ │ │
│ │ • 命令队列调度 │ │ │ document │ │ │
│ │ • 断言引擎(Chai) │ │ │ DOM 元素 │ │ │
│ │ • 命令日志/快照 │ │ │ XHR/Fetch │ │ │
│ │ • jQuery 封装 │ │ │ Service Worker│ │ │
│ └──────────┬───────────┘ │ └──────────────┘ │ │
│ │ └──────────────────────┘ │
│ │ HTTP(S) / XHR │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ Cypress Proxy │◄── 拦截/篡改网络流量 │
│ │ (浏览器内代理) │ │
│ └──────────────────────┘ │
└─────────────────────┬───────────────────────────────┘
│ WebSocket / IPC
▼
┌─────────────────────────────────────────────────────┐
│ Node.js Server 进程 │
│ │
│ • 文件系统操作(截图/录屏/读写文件) │
│ • cy.task() / cy.exec() 系统级任务 │
│ • 浏览器启动与管理(CDP / WebDriver BiDi / CDP+) │
│ • 网络请求(cy.request 直接发 HTTP,不经过浏览器) │
│ • 插件加载与配置管理 │
└─────────────────────────────────────────────────────┘
三个关键机制
1. 浏览器内运行(In-Browser Execution)
Cypress 的测试代码直接在浏览器中执行,因此可以原生访问:
window、document、DOM 元素------无需序列化、无需跨进程协议- XHR / Fetch 请求------直接拦截和修改
- Cookie、localStorage、sessionStorage
- 应用内部的 JS 对象、函数、定时器、Service Worker
没有 WebDriver 的远程命令序列化/反序列化开销,没有对象在网络上传输,一切都在同一个 JS 运行时中完成。
2. Node.js Server 进程(后端能力)
浏览器里干不了的事,交给 Node.js 进程:
- 截图和录屏文件写入磁盘
cy.task()执行系统级任务(操作数据库、调用外部脚本等)cy.exec()执行系统命令cy.request()直接发起 HTTP 请求(不经过浏览器,适合 API 测试和前置数据准备)- 浏览器进程的启动和控制
- 插件系统运行在 Node 端
Cypress 与 Node 进程之间通过 WebSocket/IPC 持续通信、同步状态、互相委托任务。
3. HTTP(S) 代理与网络控制
Cypress 在浏览器层设置了代理,所有进出被测应用的网络流量都会经过 Cypress:
cy.intercept()拦截 XHR/Fetch 请求,修改请求/响应- 可以直接 mock 接口返回,模拟错误状态码(500、404、超时)
- 测试时阻止 Google Analytics 等第三方脚本加载
这不是事后抓包,而是在请求发出的那一刻就能拦截和修改。
以一次 cy.get("#btn").click() 完整全过程
- 测试代码入队 :
cy.get("#btn")和.click()不会立即执行,而是被放入 Cypress 内部的命令队列(Command Queue) - Test Runner 调度:Cypress Test Runner 按顺序从队列取出命令
cy.get("#btn")执行 :- 在浏览器内通过
document.querySelector("#btn")查找元素 - 自动等待:如果元素不存在,Cypress 会持续重试查找(默认 4 秒),直到元素出现
- 找到后,用 jQuery 包装元素,作为命令结果传递给下一个命令
- 同时记录命令日志、截取 DOM 快照(用于时间旅行调试)
- 在浏览器内通过
.click()执行 :- 等待元素可见(display 不是 none、visibility 不是 hidden)
- 等待元素可交互(没有被遮挡、没有 disabled、没有动画进行中)
- 等待元素稳定(坐标不再变化)
- 所有条件满足后,在浏览器内触发原生点击事件(不是模拟鼠标坐标,而是真正的 DOM 事件)
- 应用响应:被测应用的 click 事件监听器执行,可能触发 AJAX、页面跳转、DOM 更新
- Cypress 感知变化:由于运行在浏览器内,Cypress 实时感知 DOM 变化、网络请求完成
- 命令完成:记录执行结果、快照,继续处理队列中下一条命令
- 断言执行 :如果后面有
.should("be.visible")或.should("have.text", "..."),Cypress 会自动重试断言直到通过或超时
⭕ 关键区别:整个过程没有 HTTP 远程调用,没有 WebDriver 中间层,没有对象序列化。命令在浏览器内直接执行,网络开销几乎为零。
02 核心架构:双进程 + 命令队列 + 自动等待
双进程模型
Cypress 由两个独立进程组成:
- 浏览器进程(前端):运行 Test Runner 和被测应用,负责页面交互、DOM 操作、断言、命令日志、快照
- Node.js 进程(后端):负责文件系统、系统任务、浏览器控制、网络请求
两个进程通过 WebSocket 保持通信。这种设计让 Cypress 同时具备"浏览器内原生访问"和"操作系统级能力"。
命令队列(Command Queue)
这是 Cypress 最独特的设计之一。cy.get()、cy.click()、cy.type() 等命令不是同步执行的,它们全部返回一个 Cypress 包装对象,实际执行被排入内部命令队列,由 Test Runner 按顺序异步执行。
javascript
// 这段代码看起来是同步的,但实际是命令排队
cy.visit("/login")
cy.get("#username").type("tom") // 不会立即执行
cy.get("#password").type("123456") // 排队等待
cy.get("#login-btn").click() // 排队等待
cy.contains("欢迎回来").should("be.visible") // 断言也排队
这意味着你不能这样写:
javascript
// ❌ 错误:username 还没拿到,cy.get 返回的是 Cypress 包装对象,不是 DOM 元素
const username = cy.get("#username")
console.log(username) // 不是你想要的
// ✅ 正确:用 .then() 访问命令结果
cy.get("#username").then(($el) => {
console.log($el.text())
})
内置自动等待(Auto-Retry)
Cypress 的每个命令都有内置的自动等待和重试机制:
| 等待条件 | 说明 |
|---|---|
| 元素存在于 DOM | cy.get() 默认等待元素出现 |
| 元素可见 | display/visibility/opacity 检查 |
| 元素可交互 | 未被遮挡、未 disabled |
| 元素稳定 | 位置和尺寸不再变化 |
| 动画完成 | 等待 CSS transition/animation 结束 |
| 网络请求完成 | cy.intercept() 等待响应 |
断言也有自动重试:.should() 会持续重试直到通过或超时(默认 4 秒)。这大幅减少了 sleep() 和手动 wait() 的需要。
底层浏览器控制协议
Cypress 根据浏览器类型使用不同的底层协议:
| 浏览器 | 控制协议 | 说明 |
|---|---|---|
| Chrome / Edge / Electron | CDP(Chrome DevTools Protocol) | Chromium 原生协议,最成熟 |
| Firefox 135+ | WebDriver BiDi | 自 Cypress 14.1.0(2025年2月)起默认使用;Cypress 15 起不再支持 CDP for Firefox |
| WebKit(Safari 技术预览) | CDP+ | Playwright 提供的扩展协议,实验性支持 |
注意:这些底层协议对用户是透明的,你只需要写 cy.visit()、cy.get(),Cypress 自动选择对应协议。
时间旅行调试(Time Travel)
每条命令执行时,Cypress 都会:
- 截取 DOM 快照
- 记录命令前后的页面状态
- 记录网络请求和控制台输出
在 Test Runner 的命令日志中,点击任意一条命令,页面会回到那条命令执行时的状态------DOM、样式、网络请求全部回退。这是 Selenium/Playwright 都不具备的调试体验。
03 Cypress 高频常用核心方法
Cypress API 基于 Mocha 测试框架(describe/it)+ Chai 断言(expect/should)+ jQuery 元素操作。全部命令自带自动等待。
🌐 页面导航类
cy.visit(url):访问页面,自动等待页面加载完成cy.go("back")/cy.go("forward"):浏览器前进后退cy.reload():刷新页面
🖱️ 元素交互类
cy.get(selector):通过 CSS 选择器获取元素(推荐用data-cy属性定位)cy.contains(text):通过文本内容查找元素.click():点击元素,自动等待可见和可交互.type(text):输入文本,模拟逐字符键盘输入.clear():清空输入框.check()/.uncheck():勾选/取消复选框.select(value):下拉选择.dblclick():双击.rightclick():右键.trigger(eventName):触发任意 DOM 事件
🔍 断言类
Cypress 支持两种断言风格:
javascript
// BDD 风格(should)
cy.get("#title").should("be.visible")
cy.get("#title").should("have.text", "欢迎回来")
cy.get("#list li").should("have.length", 5)
cy.get("#email").should("have.value", "tom@test.com")
cy.get("#btn").should("be.disabled")
// TDD 风格(expect)
cy.get("#title").then(($el) => {
expect($el.text()).to.include("欢迎")
expect($el).to.be.visible
})
常用断言语义:
be.visible/be.hidden/exist/not.existhave.text/contain/have.value/have.attrhave.class/have.css/have.lengthbe.checked/be.disabled/be.enabledbe.empty/have.focus
📡 网络拦截与 Mock(Cypress 王牌能力)
cy.intercept() 是 Cypress 最强大的 API 之一:
javascript
// 拦截 GET 请求并 mock 返回
cy.intercept("GET", "/api/users", {
statusCode: 200,
body: [{ id: 1, name: "Tom" }]
}).as("getUsers")
cy.visit("/users")
cy.wait("@getUsers") // 等待请求完成
// 拦截 POST 请求,只修改状态码
cy.intercept("POST", "/api/login", { statusCode: 500 }).as("loginFail")
// 拦截并动态修改响应
cy.intercept("GET", "/api/data", (req) => {
req.reply((res) => {
res.send({ ...res.body, modified: true })
})
})
🔑 前置数据准备与 API 测试
javascript
// cy.request() 直接发 HTTP 请求,不经过浏览器
beforeEach(() => {
// 通过 API 直接登录,比 UI 登录快 10 倍
cy.request({
method: "POST",
url: "/api/login",
body: { user: "tom", pass: "123456" }
}).then((resp) => {
window.localStorage.setItem("token", resp.body.token)
})
})
// API 测试
it("返回用户列表", () => {
cy.request("/api/users").its("status").should("eq", 200)
cy.request("/api/users").its("body").should("have.length", 10)
})
📁 Fixtures 数据驱动
javascript
// cypress/fixtures/user.json
// { "name": "Tom", "email": "tom@test.com" }
cy.fixture("user.json").then((user) => {
cy.get("#name").type(user.name)
cy.get("#email").type(user.email)
})
🎛️ 自定义命令
javascript
// cypress/support/commands.js
Cypress.Commands.add("login", (user, pass) => {
cy.request("POST", "/api/login", { user, pass })
})
Cypress.Commands.add("getByTestId", (id) => {
return cy.get(`[data-cy="${id}"]`)
})
// 用例中直接使用
cy.login("tom", "123456")
cy.getByTestId("submit-btn").click()
🖥️ iframe 处理
iframe 是 Cypress 的已知痛点,需要手动处理:
javascript
// 需要安装 cypress-iframe 插件或手动封装
cy.iframe("#my-iframe").find("#button-inside").click()
// 或者原生方式
cy.get("#my-iframe")
.its("0.contentDocument.body")
.should("not.be.empty")
.then(cy.wrap)
.find("#button-inside")
.click()
📸 截图、录屏
javascript
cy.screenshot() // 截图整个页面
cy.get("#card").screenshot() // 截图单个元素
// 失败时自动截图(默认开启)
// 录屏在 cypress.config.js 中配置 video: true
⏱️ 等待相关(极少需要手动写)
javascript
cy.wait(1000) // 强制等待(尽量避免)
cy.wait("@getUsers") // 等待网络请求完成(推荐)
cy.waitUntil(() => ...) // 需要插件 cypress-wait-until
完整示例
javascript
describe("用户登录流程", () => {
beforeEach(() => {
cy.visit("/login")
})
it("正确账号密码登录成功", () => {
cy.get("#username").type("tom")
cy.get("#password").type("123456")
cy.get("#login-btn").click()
// 等待跳转和欢迎信息
cy.url().should("include", "/dashboard")
cy.contains("欢迎回来,tom").should("be.visible")
})
it("错误密码提示错误信息", () => {
cy.intercept("POST", "/api/login", { statusCode: 401 }).as("loginFail")
cy.get("#username").type("tom")
cy.get("#password").type("wrong")
cy.get("#login-btn").click()
cy.wait("@loginFail")
cy.contains("用户名或密码错误").should("be.visible")
})
})
04 性能深度解析(Cypress vs Selenium vs Playwright)
| 维度 | Selenium | Playwright | Cypress |
|---|---|---|---|
| 运行位置 | 浏览器外部 | 浏览器外部(Node Server) | 浏览器内部(同 run loop) |
| 通信模型 | HTTP 短连接,每步操作新建请求 | WebSocket 长连接 | 浏览器内直接调用,无网络开销 |
| 中间层 | WebDriver 驱动 | Playwright Core(Node.js) | 无(注入浏览器执行) |
| 元素访问 | 远程协议序列化/反序列化 | WebSocket 传输 | 原生 JS 访问,零序列化 |
| 自动等待 | 无,需手写 WebDriverWait | 内置,Core 层实现 | 内置,命令级 + 断言级双重重试 |
| 单步延迟 | HTTP 往返开销最大 | WebSocket 通信小 | 最小(同进程函数调用) |
| 调试体验 | 截图+日志,事后排查 | trace viewer 录屏回放 | 时间旅行,每条命令 DOM 快照 |
| 网络拦截 | 需代理或浏览器扩展 | page.route() 内置 |
cy.intercept() 内置,浏览器层代理 |
| 跨浏览器 | 全部浏览器(含 IE) | Chromium/Firefox/WebKit | Chromium/Firefox/Edge/WebKit(实验) |
| 多标签页 | ✅ 原生支持 | ✅ 原生支持 | ❌ 不支持(同源限制) |
| 跨域测试 | ✅ 无限制 | ✅ 无限制 | ⚠️ 需 cy.origin(),有限制 |
| 语言支持 | Python/Java/JS/C#/Ruby/PHP | Python/Java/JS/C# | 仅 JavaScript/TypeScript |
| 并行执行 | 需自建 Selenium Grid | 内置免费 | 付费(Cypress Cloud) |
| 组件测试 | ❌ | ❌(需第三方) | ✅ 内置(React/Vue/Angular/Svelte) |
为什么 Cypress "快"?
Cypress 的速度优势来自架构:
- 零网络开销 :测试代码和应用在同一个浏览器进程内,
cy.get("#btn")本质上就是一次document.querySelector("#btn"),没有 HTTP 请求、没有 WebSocket 消息、没有序列化 - 自动等待减少重试:不像 Selenium 需要手动写显式等待或粗暴 sleep,Cypress 的命令级自动等待确保"等到能操作才操作",减少因时机不对导致的失败重试
- API 前置登录 :用
cy.request()直接调 API 准备数据,比 UI 操作快一个数量级,这是 Cypress 官方推荐的最佳实践
为什么 Cypress 有时"看起来慢"?
- 命令队列开销:每条命令都要经过入队、调度、快照、日志记录,单步有微小开销
- 默认自动等待全套检查:可见性、可交互、稳定性全部检查,简单页面相比"裸跑"会有肉眼可见的等待
- Test Runner 开销 :命令日志、DOM 快照、iframe 通信都有性能消耗(
cypress run无头模式比cypress open快很多) - 同源 iframe 隔离:Test Runner 和应用在两个 iframe 中,跨 iframe 通信有开销
05 ✅ Cypress 优势 & ❌ 劣势
✅ 优势
-
浏览器内运行,零网络开销:测试代码与应用同进程,元素访问就是原生 JS 调用,没有 WebDriver 的 HTTP 往返和序列化开销,执行速度和稳定性天然占优。
-
开发者体验天花板:时间旅行调试、实时重载(改代码自动重跑)、命令日志可视化、DOM 快照、控制台输出集成------Test Runner 的调试体验是所有框架中最好的,没有之一。
-
All-in-One,零配置开箱即用 :内置 Mocha(测试框架)+ Chai(断言)+ jQuery(DOM 操作)+ Sinon(Mock/Stub),
npm install cypress一条命令装好,不用拼装 10 个库。 -
命令级 + 断言级双重自动等待 :每条命令自动等待元素可见可交互,
.should()断言自动重试,几乎不需要写sleep(),脚本简洁且通过率高。 -
网络拦截能力极强 :
cy.intercept()在浏览器代理层拦截 XHR/Fetch,可以 mock 任意请求/响应、修改状态码、模拟超时和错误,前后端分离项目的测试利器。 -
组件测试原生支持:Cypress 不只是 E2E 工具,还内置组件测试能力,支持 React、Vue、Angular、Svelte 等框架,可以像单元测试一样隔离测试单个组件。
-
前端框架无关但对 SPA 特别友好:React/Vue/Angular 现代 SPA 的动态渲染、异步加载、客户端路由,Cypress 都能稳定处理。
-
丰富的插件生态 :
cypress-iframe、cypress-real-events、cypress-file-upload、@testing-library/cypress等社区插件覆盖大部分边缘场景。 -
无障碍测试和覆盖率:Cypress 14+ 内置 Accessibility 测试和 UI Coverage(付费),可以检测页面无障碍问题和未测试区域。
-
社区大、文档好:GitHub Star 超 48k,官方文档质量极高,常见问题 Stack Overflow 答案丰富。
❌ 劣势
-
仅限 JavaScript/TypeScript:这是架构决定的------测试代码跑在浏览器里,只能用 JS。Python/Java 团队需要切换语言,学习成本不可忽视。
-
同源策略限制 :Cypress 在浏览器内运行,受浏览器同源策略约束。跨多个域名/子域的测试需要用
cy.origin()(Cypress 14 后必须显式使用),配置和使用都有额外复杂度。 -
不支持多标签页/多窗口 :
target="_blank"的链接无法在新标签页中测试,需要用 workaround(去掉 target 属性或断言 href)。这是浏览器内架构的固有局限。 -
iframe 处理繁琐 :虽然被测应用本身在 iframe 中,但嵌套 iframe(应用内的 iframe)需要额外插件或手动封装,不如 Playwright 的
frameLocator()简洁。 -
并行执行付费:Cypress 开源版不支持并行执行,要跑并行必须订阅 Cypress Cloud(Team 计划 $67/月起)。Playwright 的并行完全免费。
-
不支持原生移动端 App:只测浏览器内运行的应用,无法驱动 iOS/Android 原生 App(.apk/.ipa)。移动端只能做浏览器视口模拟。
-
不支持 Safari/WebKit 生产级测试:WebKit 支持仍是实验性的,Safari 上的兼容性问题较多。如果 Safari 用户占比高,Cypress 不是最佳选择。
-
文件上传需要插件 :原生
cy.get('input[type=file]').selectFile()在较新版本已支持,但复杂上传场景(拖拽上传、大文件)仍需cypress-file-upload插件。 -
多 Tab、文件下载、新窗口场景弱:这些是 Cypress 架构的盲区,需要 workaround,不如 Playwright 开箱即用。
-
长期运行内存问题:长时间运行大量 spec 时,浏览器进程可能出现内存泄漏(Cypress 15 持续在修复),CI 环境需要定期回收浏览器实例。
06 选型建议:什么时候用 Cypress?
✅ 选择 Cypress 的场景
- 前端团队主导的测试:团队本身就是 JS/TS 技术栈,不想切换语言
- 现代 SPA 应用:React/Vue/Angular 单页应用,动态渲染多、AJAX 频繁
- 组件测试 + E2E 一体化:想在同一个框架里做组件测试和端到端测试
- 重视调试体验:测试用例需要频繁调试,时间旅行和实时重载能大幅提效
- 前后端分离项目 :需要大量 mock API、模拟网络异常,
cy.intercept()是利器 - TDD/BDD 开发模式:边开发边写测试,Cypress 的实时重载和可视化反馈非常适合
- CI 回归测试 :
cypress run无头模式稳定,截图录屏自动留存
❌ 不适合 Cypress 的场景
- 团队技术栈非 JS:Python/Java 为主的团队,切换语言成本高,选 Playwright 或 Selenium
- 跨域名/多标签页复杂场景:SSO 登录跳转多个域名、需要测试新窗口打开页面------选 Playwright
- 需要 Safari 生产级覆盖:WebKit 支持不成熟,选 Playwright
- 大规模并行测试且预算有限:Cypress Cloud 并行收费,Playwright 免费并行
- 需要测试原生移动端 App:选 Appium
- 老旧 IE 浏览器兼容测试:Cypress 不支持 IE,选 Selenium
07 核心总结
Cypress 的核心竞争力不是"什么都能测",而是在浏览器内运行这一架构决策带来的极致开发者体验和稳定性。
✅ 测试代码跑在浏览器内,与应用同进程,零网络开销 ✅ 命令队列 + 双重自动等待,解决 UI 自动化的"时机问题" ✅ 时间旅行调试,每条命令 DOM 快照,调试体验行业最佳 ✅ All-in-One 零配置,Mocha + Chai + jQuery + Sinon 全家桶 ✅ cy.intercept() 网络拦截,前后端分离测试利器 ✅ 内置组件测试,React/Vue/Angular/Svelte 全覆盖 ✅ Node.js 后端进程补齐系统级能力
但它的边界同样明确:
❌ 只能用 JS/TS,多语言团队不友好 ❌ 同源策略限制,跨域和多标签页是架构盲区 ❌ 并行执行收费,WebKit 支持不成熟 ❌ iframe 和文件上传等场景需要插件补位
前端 JS 团队、现代 SPA、重视调试体验、需要组件+E2E 一体化------这是 Cypress 的甜蜜区。跨域多标签、多语言、Safari 全覆盖、大规模免费并行------去看 Playwright。