中文资讯 掘金 AI Wed, 26 Aug 2026 正文 ok 1719 words

Cypress 自动化全解:浏览器内运行架构、命令队列、高频 API 与项目选型指南

英文标题:Cypress 自动化全解:浏览器内运行架构、命令队列、高频 API 与项目选型指南

在 Web 自动化测试领域,Selenium 长期是行业标准,但稳定性差、等待机制笨拙、脚本维护成本高,一直是测试工程师的日常痛点。Playwright 凭借直连浏览器内核的架构异军突起,而 Cypr
打开原文相关 2 · 机会 2
media3.2 KB media589 B media16.4 KB media6.5 KB

中文

在 Web 自动化测试领域,Selenium 长期是行业标准,但稳定性差、等待机制笨拙、脚本维护成本高,一直是测试工程师的日常痛点。Playwright 凭借直连浏览器内核的架构异军突起,而 Cypress 走了一条完全不同的路——它直接跑在浏览器内部,与被测应用共享同一个运行循环(run loop),带来了前所未有的调试体验和执行稳定性。

今天我们从零拆解 Cypress 的工作原理、双进程架构、命令队列调度、常用核心方法、与 Selenium/Playwright 的性能对比、完整优劣势 ,帮你彻底吃透这款"开发者体验最好"的自动化测试框架。

01 Cypress 完整工作原理(结合架构图)

在这里插入图片描述

Cypress 最核心的设计决策是:测试代码运行在浏览器内部,而不是浏览器外部。

Selenium 的链路是 测试脚本 → HTTP 请求 → WebDriver 驱动 → 浏览器,每一步都是跨进程远程命令。Cypress 完全反其道而行——它把测试脚本注入浏览器,在浏览器里用 iframe 承载被测应用,测试代码和应用代码跑在同一个浏览器进程中。

整体架构

┌─────────────────────────────────────────────────────┐
│                   浏览器(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() 完整全过程

  1. 测试代码入队 :cy.get("#btn") 和 .click() 不会立即执行,而是被放入 Cypress 内部的命令队列 (Command Queue)
  2. Test Runner 调度 :Cypress Test Runner 按顺序从队列取出命令
  3. cy.get("#btn") 执行:
    • 在浏览器内通过 document.querySelector("#btn") 查找元素
    • 自动等待 :如果元素不存在,Cypress 会持续重试查找(默认 4 秒),直到元素出现
    • 找到后,用 jQuery 包装元素,作为命令结果传递给下一个命令
    • 同时记录命令日志、截取 DOM 快照(用于时间旅行调试)
  4. .click() 执行:
    • 等待元素可见 (display 不是 none、visibility 不是 hidden)
    • 等待元素可交互 (没有被遮挡、没有 disabled、没有动画进行中)
    • 等待元素稳定 (坐标不再变化)
    • 所有条件满足后,在浏览器内触发原生点击事件 (不是模拟鼠标坐标,而是真正的 DOM 事件)
  5. 应用响应 :被测应用的 click 事件监听器执行,可能触发 AJAX、页面跳转、DOM 更新
  6. Cypress 感知变化 :由于运行在浏览器内,Cypress 实时感知 DOM 变化、网络请求完成
  7. 命令完成 :记录执行结果、快照,继续处理队列中下一条命令
  8. 断言执行 :如果后面有 .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 按顺序异步执行。

cy.visit("/login")
cy.get("#username").type("tom")      
cy.get("#password").type("123456")  
cy.get("#login-btn").click()         
cy.contains("欢迎回来").should("be.visible")  

这意味着你不能 这样写:

const username = cy.get("#username")
console.log(username)  


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 支持两种断言风格:

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")


cy.get("#title").then(($el) => {
  expect($el.text()).to.include("欢迎")
  expect($el).to.be.visible
})

常用断言语义:

  • be.visible / be.hidden / exist / not.exist
  • have.text / contain / have.value / have.attr
  • have.class / have.css / have.length
  • be.checked / be.disabled / be.enabled
  • be.empty / have.focus

📡 网络拦截与 Mock(Cypress 王牌能力)

cy.intercept() 是 Cypress 最强大的 API 之一:

cy.intercept("GET", "/api/users", {
  statusCode: 200,
  body: [{ id: 1, name: "Tom" }]
}).as("getUsers")

cy.visit("/users")
cy.wait("@getUsers")  


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 测试

beforeEach(() => {
  
  cy.request({
    method: "POST",
    url: "/api/login",
    body: { user: "tom", pass: "123456" }
  }).then((resp) => {
    window.localStorage.setItem("token", resp.body.token)
  })
})


it("返回用户列表", () => {
  cy.request("/api/users").its("status").should("eq", 200)
  cy.request("/api/users").its("body").should("have.length", 10)
})

📁 Fixtures 数据驱动

cy.fixture("user.json").then((user) => {
  cy.get("#name").type(user.name)
  cy.get("#email").type(user.email)
})

🎛️ 自定义命令

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 的已知痛点,需要手动处理:

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()

📸 截图、录屏

cy.screenshot()                          
cy.get("#card").screenshot()             

⏱️ 等待相关(极少需要手动写)

cy.wait(1000)                    
cy.wait("@getUsers")             
cy.waitUntil(() => ...)          

完整示例

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 的速度优势来自架构:

  1. 零网络开销 :测试代码和应用在同一个浏览器进程内,cy.get("#btn") 本质上就是一次 document.querySelector("#btn"),没有 HTTP 请求、没有 WebSocket 消息、没有序列化
  2. 自动等待减少重试 :不像 Selenium 需要手动写显式等待或粗暴 sleep,Cypress 的命令级自动等待确保"等到能操作才操作",减少因时机不对导致的失败重试
  3. API 前置登录 :用 cy.request() 直接调 API 准备数据,比 UI 操作快一个数量级,这是 Cypress 官方推荐的最佳实践

为什么 Cypress 有时"看起来慢"?

  1. 命令队列开销 :每条命令都要经过入队、调度、快照、日志记录,单步有微小开销
  2. 默认自动等待全套检查 :可见性、可交互、稳定性全部检查,简单页面相比"裸跑"会有肉眼可见的等待
  3. Test Runner 开销 :命令日志、DOM 快照、iframe 通信都有性能消耗(cypress run 无头模式比 cypress open 快很多)
  4. 同源 iframe 隔离 :Test Runner 和应用在两个 iframe 中,跨 iframe 通信有开销

05 ✅ Cypress 优势 & ❌ 劣势

✅ 优势

  1. 浏览器内运行,零网络开销 :测试代码与应用同进程,元素访问就是原生 JS 调用,没有 WebDriver 的 HTTP 往返和序列化开销,执行速度和稳定性天然占优。

  2. 开发者体验天花板 :时间旅行调试、实时重载(改代码自动重跑)、命令日志可视化、DOM 快照、控制台输出集成——Test Runner 的调试体验是所有框架中最好的,没有之一。

  3. All-in-One,零配置开箱即用 :内置 Mocha(测试框架)+ Chai(断言)+ jQuery(DOM 操作)+ Sinon(Mock/Stub),npm install cypress 一条命令装好,不用拼装 10 个库。

  4. 命令级 + 断言级双重自动等待 :每条命令自动等待元素可见可交互,.should() 断言自动重试,几乎不需要写 sleep(),脚本简洁且通过率高。

  5. 网络拦截能力极强 :cy.intercept() 在浏览器代理层拦截 XHR/Fetch,可以 mock 任意请求/响应、修改状态码、模拟超时和错误,前后端分离项目的测试利器。

  6. 组件测试原生支持 :Cypress 不只是 E2E 工具,还内置组件测试能力,支持 React、Vue、Angular、Svelte 等框架,可以像单元测试一样隔离测试单个组件。

  7. 前端框架无关但对 SPA 特别友好 :React/Vue/Angular 现代 SPA 的动态渲染、异步加载、客户端路由,Cypress 都能稳定处理。

  8. 丰富的插件生态 :cypress-iframe、cypress-real-events、cypress-file-upload、@testing-library/cypress 等社区插件覆盖大部分边缘场景。

  9. 无障碍测试和覆盖率 :Cypress 14+ 内置 Accessibility 测试和 UI Coverage(付费),可以检测页面无障碍问题和未测试区域。

  10. 社区大、文档好 :GitHub Star 超 48k,官方文档质量极高,常见问题 Stack Overflow 答案丰富。

❌ 劣势

  1. 仅限 JavaScript/TypeScript :这是架构决定的——测试代码跑在浏览器里,只能用 JS。Python/Java 团队需要切换语言,学习成本不可忽视。

  2. 同源策略限制 :Cypress 在浏览器内运行,受浏览器同源策略约束。跨多个域名/子域的测试需要用 cy.origin()(Cypress 14 后必须显式使用),配置和使用都有额外复杂度。

  3. 不支持多标签页/多窗口 :target="_blank" 的链接无法在新标签页中测试,需要用 workaround(去掉 target 属性或断言 href)。这是浏览器内架构的固有局限。

  4. iframe 处理繁琐 :虽然被测应用本身在 iframe 中,但嵌套 iframe(应用内的 iframe)需要额外插件或手动封装,不如 Playwright 的 frameLocator() 简洁。

  5. 并行执行付费 :Cypress 开源版不支持并行执行,要跑并行必须订阅 Cypress Cloud(Team 计划 $67/月起)。Playwright 的并行完全免费。

  6. 不支持原生移动端 App :只测浏览器内运行的应用,无法驱动 iOS/Android 原生 App(.apk/.ipa)。移动端只能做浏览器视口模拟。

  7. 不支持 Safari/WebKit 生产级测试 :WebKit 支持仍是实验性的,Safari 上的兼容性问题较多。如果 Safari 用户占比高,Cypress 不是最佳选择。

  8. 文件上传需要插件 :原生 cy.get('input[type=file]').selectFile() 在较新版本已支持,但复杂上传场景(拖拽上传、大文件)仍需 cypress-file-upload 插件。

  9. 多 Tab、文件下载、新窗口场景弱 :这些是 Cypress 架构的盲区,需要 workaround,不如 Playwright 开箱即用。

  10. 长期运行内存问题 :长时间运行大量 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。

English / 原文

在 Web 自动化测试领域,Selenium 长期是行业标准,但稳定性差、等待机制笨拙、脚本维护成本高,一直是测试工程师的日常痛点。Playwright 凭借直连浏览器内核的架构异军突起,而 Cypress 走了一条完全不同的路——它直接跑在浏览器内部,与被测应用共享同一个运行循环(run loop),带来了前所未有的调试体验和执行稳定性。

今天我们从零拆解 Cypress 的工作原理、双进程架构、命令队列调度、常用核心方法、与 Selenium/Playwright 的性能对比、完整优劣势 ,帮你彻底吃透这款"开发者体验最好"的自动化测试框架。

01 Cypress 完整工作原理(结合架构图)

在这里插入图片描述

Cypress 最核心的设计决策是:测试代码运行在浏览器内部,而不是浏览器外部。

Selenium 的链路是 测试脚本 → HTTP 请求 → WebDriver 驱动 → 浏览器,每一步都是跨进程远程命令。Cypress 完全反其道而行——它把测试脚本注入浏览器,在浏览器里用 iframe 承载被测应用,测试代码和应用代码跑在同一个浏览器进程中。

整体架构

┌─────────────────────────────────────────────────────┐
│                   浏览器(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() 完整全过程

  1. 测试代码入队 :cy.get("#btn") 和 .click() 不会立即执行,而是被放入 Cypress 内部的命令队列 (Command Queue)
  2. Test Runner 调度 :Cypress Test Runner 按顺序从队列取出命令
  3. cy.get("#btn") 执行:
    • 在浏览器内通过 document.querySelector("#btn") 查找元素
    • 自动等待 :如果元素不存在,Cypress 会持续重试查找(默认 4 秒),直到元素出现
    • 找到后,用 jQuery 包装元素,作为命令结果传递给下一个命令
    • 同时记录命令日志、截取 DOM 快照(用于时间旅行调试)
  4. .click() 执行:
    • 等待元素可见 (display 不是 none、visibility 不是 hidden)
    • 等待元素可交互 (没有被遮挡、没有 disabled、没有动画进行中)
    • 等待元素稳定 (坐标不再变化)
    • 所有条件满足后,在浏览器内触发原生点击事件 (不是模拟鼠标坐标,而是真正的 DOM 事件)
  5. 应用响应 :被测应用的 click 事件监听器执行,可能触发 AJAX、页面跳转、DOM 更新
  6. Cypress 感知变化 :由于运行在浏览器内,Cypress 实时感知 DOM 变化、网络请求完成
  7. 命令完成 :记录执行结果、快照,继续处理队列中下一条命令
  8. 断言执行 :如果后面有 .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 按顺序异步执行。

cy.visit("/login")
cy.get("#username").type("tom")      
cy.get("#password").type("123456")  
cy.get("#login-btn").click()         
cy.contains("欢迎回来").should("be.visible")  

这意味着你不能 这样写:

const username = cy.get("#username")
console.log(username)  


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 支持两种断言风格:

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")


cy.get("#title").then(($el) => {
  expect($el.text()).to.include("欢迎")
  expect($el).to.be.visible
})

常用断言语义:

  • be.visible / be.hidden / exist / not.exist
  • have.text / contain / have.value / have.attr
  • have.class / have.css / have.length
  • be.checked / be.disabled / be.enabled
  • be.empty / have.focus

📡 网络拦截与 Mock(Cypress 王牌能力)

cy.intercept() 是 Cypress 最强大的 API 之一:

cy.intercept("GET", "/api/users", {
  statusCode: 200,
  body: [{ id: 1, name: "Tom" }]
}).as("getUsers")

cy.visit("/users")
cy.wait("@getUsers")  


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 测试

beforeEach(() => {
  
  cy.request({
    method: "POST",
    url: "/api/login",
    body: { user: "tom", pass: "123456" }
  }).then((resp) => {
    window.localStorage.setItem("token", resp.body.token)
  })
})


it("返回用户列表", () => {
  cy.request("/api/users").its("status").should("eq", 200)
  cy.request("/api/users").its("body").should("have.length", 10)
})

📁 Fixtures 数据驱动

cy.fixture("user.json").then((user) => {
  cy.get("#name").type(user.name)
  cy.get("#email").type(user.email)
})

🎛️ 自定义命令

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 的已知痛点,需要手动处理:

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()

📸 截图、录屏

cy.screenshot()                          
cy.get("#card").screenshot()             

⏱️ 等待相关(极少需要手动写)

cy.wait(1000)                    
cy.wait("@getUsers")             
cy.waitUntil(() => ...)          

完整示例

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 的速度优势来自架构:

  1. 零网络开销 :测试代码和应用在同一个浏览器进程内,cy.get("#btn") 本质上就是一次 document.querySelector("#btn"),没有 HTTP 请求、没有 WebSocket 消息、没有序列化
  2. 自动等待减少重试 :不像 Selenium 需要手动写显式等待或粗暴 sleep,Cypress 的命令级自动等待确保"等到能操作才操作",减少因时机不对导致的失败重试
  3. API 前置登录 :用 cy.request() 直接调 API 准备数据,比 UI 操作快一个数量级,这是 Cypress 官方推荐的最佳实践

为什么 Cypress 有时"看起来慢"?

  1. 命令队列开销 :每条命令都要经过入队、调度、快照、日志记录,单步有微小开销
  2. 默认自动等待全套检查 :可见性、可交互、稳定性全部检查,简单页面相比"裸跑"会有肉眼可见的等待
  3. Test Runner 开销 :命令日志、DOM 快照、iframe 通信都有性能消耗(cypress run 无头模式比 cypress open 快很多)
  4. 同源 iframe 隔离 :Test Runner 和应用在两个 iframe 中,跨 iframe 通信有开销

05 ✅ Cypress 优势 & ❌ 劣势

✅ 优势

  1. 浏览器内运行,零网络开销 :测试代码与应用同进程,元素访问就是原生 JS 调用,没有 WebDriver 的 HTTP 往返和序列化开销,执行速度和稳定性天然占优。

  2. 开发者体验天花板 :时间旅行调试、实时重载(改代码自动重跑)、命令日志可视化、DOM 快照、控制台输出集成——Test Runner 的调试体验是所有框架中最好的,没有之一。

  3. All-in-One,零配置开箱即用 :内置 Mocha(测试框架)+ Chai(断言)+ jQuery(DOM 操作)+ Sinon(Mock/Stub),npm install cypress 一条命令装好,不用拼装 10 个库。

  4. 命令级 + 断言级双重自动等待 :每条命令自动等待元素可见可交互,.should() 断言自动重试,几乎不需要写 sleep(),脚本简洁且通过率高。

  5. 网络拦截能力极强 :cy.intercept() 在浏览器代理层拦截 XHR/Fetch,可以 mock 任意请求/响应、修改状态码、模拟超时和错误,前后端分离项目的测试利器。

  6. 组件测试原生支持 :Cypress 不只是 E2E 工具,还内置组件测试能力,支持 React、Vue、Angular、Svelte 等框架,可以像单元测试一样隔离测试单个组件。

  7. 前端框架无关但对 SPA 特别友好 :React/Vue/Angular 现代 SPA 的动态渲染、异步加载、客户端路由,Cypress 都能稳定处理。

  8. 丰富的插件生态 :cypress-iframe、cypress-real-events、cypress-file-upload、@testing-library/cypress 等社区插件覆盖大部分边缘场景。

  9. 无障碍测试和覆盖率 :Cypress 14+ 内置 Accessibility 测试和 UI Coverage(付费),可以检测页面无障碍问题和未测试区域。

  10. 社区大、文档好 :GitHub Star 超 48k,官方文档质量极高,常见问题 Stack Overflow 答案丰富。

❌ 劣势

  1. 仅限 JavaScript/TypeScript :这是架构决定的——测试代码跑在浏览器里,只能用 JS。Python/Java 团队需要切换语言,学习成本不可忽视。

  2. 同源策略限制 :Cypress 在浏览器内运行,受浏览器同源策略约束。跨多个域名/子域的测试需要用 cy.origin()(Cypress 14 后必须显式使用),配置和使用都有额外复杂度。

  3. 不支持多标签页/多窗口 :target="_blank" 的链接无法在新标签页中测试,需要用 workaround(去掉 target 属性或断言 href)。这是浏览器内架构的固有局限。

  4. iframe 处理繁琐 :虽然被测应用本身在 iframe 中,但嵌套 iframe(应用内的 iframe)需要额外插件或手动封装,不如 Playwright 的 frameLocator() 简洁。

  5. 并行执行付费 :Cypress 开源版不支持并行执行,要跑并行必须订阅 Cypress Cloud(Team 计划 $67/月起)。Playwright 的并行完全免费。

  6. 不支持原生移动端 App :只测浏览器内运行的应用,无法驱动 iOS/Android 原生 App(.apk/.ipa)。移动端只能做浏览器视口模拟。

  7. 不支持 Safari/WebKit 生产级测试 :WebKit 支持仍是实验性的,Safari 上的兼容性问题较多。如果 Safari 用户占比高,Cypress 不是最佳选择。

  8. 文件上传需要插件 :原生 cy.get('input[type=file]').selectFile() 在较新版本已支持,但复杂上传场景(拖拽上传、大文件)仍需 cypress-file-upload 插件。

  9. 多 Tab、文件下载、新窗口场景弱 :这些是 Cypress 架构的盲区,需要 workaround,不如 Playwright 开箱即用。

  10. 长期运行内存问题 :长时间运行大量 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。