Cypress 简介 #
什么是 Cypress? #
Cypress 是一个新一代的前端端到端(E2E)测试框架,专为现代 Web 应用设计。它基于 Node.js 构建,可以在浏览器中运行,提供了完整的测试解决方案。与传统的 Selenium 不同,Cypress 在浏览器内部运行,能够直接访问应用程序代码,提供更快速、可靠的测试体验。
核心定位 #
text
┌─────────────────────────────────────────────────────────────┐
│ Cypress │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 测试运行器 │ │ 断言库 │ │ Mock 功能 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 时间旅行 │ │ 实时重载 │ │ 网络控制 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
Cypress 的历史 #
发展历程 #
text
2015年 ─── Cypress 项目启动
│
│ Brian Mann 创立
│ 解决 Selenium 的痛点
│
2017年 ─── 开源发布
│
│ 公开发布测试框架
│ 获得开发者关注
│
2018年 ─── Cypress 3.0
│
│ 稳定版本发布
│ 企业级功能
│
2020年 ─── Cypress 5.0
│
│ 组件测试支持
│ 网络层改进
│
2022年 ─── Cypress 10.0
│
│ 全新测试运行器
│ 配置系统重构
│
2024年 ─── Cypress 13.0
│
│ 性能大幅提升
│ 更好的调试体验
│
至今 ─── 行业标准
│
│ 超过 500 万周下载量
│ 主流企业广泛采用
里程碑版本 #
| 版本 | 时间 | 重要特性 |
|---|---|---|
| 1.0 | 2017 | 基础测试功能、实时重载 |
| 3.0 | 2018 | 稳定版发布、企业功能 |
| 4.0 | 2019 | 网络请求控制、插件系统 |
| 5.0 | 2020 | 组件测试、实验性功能 |
| 6.0 | 2021 | 网络间谍增强 |
| 7.0 | 2021 | 更快的测试执行 |
| 10.0 | 2022 | 新测试运行器、配置重构 |
| 13.0 | 2024 | 性能优化、调试增强 |
为什么选择 Cypress? #
传统测试框架的痛点 #
在使用 Selenium 等传统测试框架时,面临以下问题:
javascript
// Selenium 测试示例
const { Builder, By, until } = require('selenium-webdriver');
async function testLogin() {
// 需要手动管理等待
const driver = await new Builder().forBrowser('chrome').build();
try {
await driver.get('https://example.com/login');
// 显式等待元素出现
await driver.wait(until.elementLocated(By.id('username')), 5000);
// 操作元素
await driver.findElement(By.id('username')).sendKeys('user');
await driver.findElement(By.id('password')).sendKeys('pass');
await driver.findElement(By.id('submit')).click();
// 再次等待结果
await driver.wait(until.elementLocated(By.className('welcome')), 5000);
} finally {
await driver.quit();
}
}
Cypress 的解决方案 #
javascript
// Cypress 测试示例
describe('Login Test', () => {
it('should login successfully', () => {
cy.visit('/login');
// 自动等待和重试
cy.get('#username').type('user');
cy.get('#password').type('pass');
cy.get('#submit').click();
// 自动等待断言通过
cy.get('.welcome').should('be.visible');
});
});
Cypress 的核心特点 #
1. 架构优势 #
Cypress 运行在浏览器内部,而非外部:
text
┌─────────────────────────────────────────────────────────────┐
│ 传统 Selenium 架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 测试代码 ──> WebDriver ──> 浏览器驱动 ──> 浏览器 │
│ │ │ │ │ │
│ └───────────┴──────────────┴───────────┘ │
│ 网络延迟 + 进程间通信开销 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Cypress 架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 测试代码 ─────────────────────> 浏览器内部执行 │
│ │ │ │
│ └──────────────────────────────────┘ │
│ 直接访问,无网络延迟 │
│ │
└─────────────────────────────────────────────────────────────┘
2. 自动等待 #
无需手动添加等待逻辑:
javascript
// Cypress 自动等待
cy.get('.loading').should('not.exist'); // 等待加载完成
cy.get('.data').should('have.length', 10); // 等待数据加载
// 自动重试直到条件满足
cy.get('.status').should('contain', 'Success');
3. 时间旅行 #
可以查看每一步的页面状态:
text
┌─────────────────────────────────────────────────────────────┐
│ Cypress Test Runner │
├─────────────────────────────────────────────────────────────┤
│ │
│ ✓ visit('/login') ─── 点击查看页面快照 │
│ ✓ get('#username').type() ─── 点击查看输入后状态 │
│ ✓ get('#password').type() ─── 点击查看输入后状态 │
│ ✓ get('#submit').click() ─── 点击查看点击后状态 │
│ ✓ get('.welcome') ─── 点击查看最终状态 │
│ │
│ [页面快照预览区域] │
│ │
└─────────────────────────────────────────────────────────────┘
4. 实时重载 #
修改测试代码后自动重新运行:
bash
# 启动交互模式
npx cypress open
# 修改测试文件后自动重新运行
# 无需手动刷新或重启
5. 网络流量控制 #
完全控制网络请求:
javascript
// 拦截 API 请求
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');
// 模拟网络错误
cy.intercept('GET', '/api/data', { forceNetworkError: true });
// 延迟响应
cy.intercept('GET', '/api/slow', {
delay: 3000,
body: { data: 'delayed' }
});
6. 截图和视频 #
自动记录测试过程:
javascript
// 手动截图
cy.screenshot('login-page');
// 配置自动截图(失败时)
// cypress.config.js
module.exports = {
e2e: {
screenshotOnRunFailure: true,
video: true
}
}
Cypress 与其他测试框架对比 #
Cypress vs Selenium #
| 特性 | Cypress | Selenium |
|---|---|---|
| 架构 | 浏览器内部 | 浏览器外部 |
| 执行速度 | ✅ 快 | ⚠️ 较慢 |
| 自动等待 | ✅ 内置 | ❌ 需手动 |
| 调试体验 | ✅ 优秀 | ⚠️ 一般 |
| 跨浏览器 | ⚠️ 有限 | ✅ 全面 |
| 语言支持 | JavaScript | 多语言 |
| 学习曲线 | ✅ 低 | ⚠️ 中等 |
Cypress vs Playwright #
| 特性 | Cypress | Playwright |
|---|---|---|
| 架构 | 浏览器内部 | 浏览器外部 |
| 跨浏览器 | ⚠️ Chrome系优先 | ✅ 全面支持 |
| 并行执行 | ✅ 云端 | ✅ 本地支持 |
| API 测试 | ✅ 支持 | ✅ 支持 |
| 学习曲线 | ✅ 低 | ⚠️ 中等 |
| 社区生态 | ✅ 成熟 | 🔄 快速发展 |
Cypress vs Puppeteer #
| 特性 | Cypress | Puppeteer |
|---|---|---|
| 定位 | 测试框架 | 自动化工具 |
| 测试运行器 | ✅ 内置 | ❌ 需配置 |
| 断言库 | ✅ 内置 | ❌ 需配置 |
| 调试工具 | ✅ 完整 | ⚠️ 基础 |
| 学习曲线 | ✅ 低 | ⚠️ 中等 |
Cypress 的应用场景 #
1. E2E 测试 #
测试完整的用户流程:
javascript
describe('Shopping Cart', () => {
it('completes purchase flow', () => {
cy.visit('/products');
cy.get('.product').first().click();
cy.get('.add-to-cart').click();
cy.get('.cart-badge').should('contain', '1');
cy.get('.checkout').click();
cy.get('.order-confirmation').should('be.visible');
});
});
2. API 测试 #
测试后端 API:
javascript
describe('API Tests', () => {
it('fetches user data', () => {
cy.request('GET', '/api/users/1').then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('name');
});
});
});
3. 组件测试 #
测试独立组件:
javascript
// Button.cy.jsx
import Button from './Button';
describe('Button', () => {
it('renders with text', () => {
cy.mount(<Button>Click me</Button>);
cy.get('button').should('contain', 'Click me');
});
});
4. 视觉回归测试 #
检测 UI 变化:
javascript
describe('Visual Tests', () => {
it('matches homepage snapshot', () => {
cy.visit('/');
cy.compareSnapshot('homepage');
});
});
Cypress 的核心概念 #
测试结构 #
javascript
describe('Test Suite', () => {
before(() => {
// 所有测试之前执行一次
});
beforeEach(() => {
// 每个测试之前执行
});
it('test case', () => {
// 单个测试
});
afterEach(() => {
// 每个测试之后执行
});
after(() => {
// 所有测试之后执行一次
});
});
链式调用 #
javascript
cy.get('.item') // 选择元素
.should('be.visible') // 断言可见
.click() // 点击
.should('have.class', 'active'); // 断言类名
命令队列 #
javascript
// Cypress 命令是异步的,会加入队列
cy.visit('/'); // 命令 1
cy.get('.button').click(); // 命令 2
cy.get('.result').should('exist'); // 命令 3
// 命令按顺序执行,即使代码看起来是同步的
Cypress 的设计哲学 #
1. 开发者体验优先 #
javascript
// 清晰的错误消息
// AssertionError: Timed out retrying after 4000ms:
// Expected element '.button' to be visible
// 详细的堆栈跟踪
// at Context.<anonymous> (login.spec.js:15:12)
2. 约定优于配置 #
text
my-project/
├── cypress/
│ ├── e2e/ # E2E 测试目录
│ │ └── login.cy.js # 测试文件
│ ├── fixtures/ # 测试数据
│ ├── support/ # 支持文件
│ └── screenshots/ # 截图
├── cypress.config.js # 配置文件
└── package.json
3. 可靠性至上 #
- 自动等待和重试
- 稳定的选择器策略
- 完整的错误追踪
Cypress 的局限性 #
已知限制 #
- 跨域限制:单源策略限制跨域测试
- 浏览器支持:主要支持 Chrome 系浏览器
- 多标签页:不支持多标签页测试
- 文件上传:复杂文件上传需要特殊处理
解决方案 #
javascript
// 跨域测试 - 使用 cy.origin()
cy.origin('https://other-domain.com', () => {
cy.get('.element').click();
});
// 多标签页 - 在同一标签页中操作
cy.get('a[target="_blank"]').invoke('removeAttr', 'target').click();
// 文件上传 - 使用 cypress-file-upload 插件
cy.get('input[type="file"]').attachFile('test.pdf');
学习路径 #
text
入门阶段
├── 安装与配置
├── 编写第一个测试
├── 使用选择器
└── 基本断言
进阶阶段
├── 用户交互
├── 异步处理
├── 网络请求模拟
└── 测试数据管理
高级阶段
├── 自定义命令
├── 页面对象模式
├── 视觉测试
└── CI/CD 集成
实战阶段
├── React 应用测试
├── Vue 应用测试
├── API 测试
└── 最佳实践
下一步 #
现在你已经了解了 Cypress 的基本概念,接下来学习 安装与配置 开始实际使用 Cypress!
最后更新:2026-03-28