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 的局限性 #

已知限制 #

  1. 跨域限制:单源策略限制跨域测试
  2. 浏览器支持:主要支持 Chrome 系浏览器
  3. 多标签页:不支持多标签页测试
  4. 文件上传:复杂文件上传需要特殊处理

解决方案 #

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