跳转至

Cypress 前端测试 — 在真实浏览器中运行的端到端与组件测试框架


一句话说明

Cypress 是一个完全运行在浏览器内部的测试框架,测试代码和应用代码在同一个进程中执行,调试直观(能看到每一步的 DOM 快照),支持 E2E 测试和组件测试,当前版本 v13.x(2025年稳定)。


安装与配置

# 安装 Cypress
npm install -D cypress

# 首次打开(自动创建目录结构)
npx cypress open

# 直接运行测试(无头模式,适合 CI)
npx cypress run

# 查看版本
npx cypress --version
// cypress.config.js — 配置文件
import { defineConfig } from 'cypress';

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:5173',      // 应用基础 URL
    specPattern: 'cypress/e2e/**/*.cy.ts', // 测试文件匹配规则
    supportFile: 'cypress/support/e2e.ts', // 支持文件路径
    viewportWidth: 1280,                   // 浏览器宽度
    viewportHeight: 720,                   // 浏览器高度
    video: false,                          // 是否录制视频(CI 建议关闭节省空间)
    screenshotOnRunFailure: true,          // 失败时自动截图
    defaultCommandTimeout: 10000,         // 命令超时时间(ms)
    retries: {
      runMode: 2,                          // CI 运行时失败重试2次
      openMode: 0,                         // 开发时不重试
    },
  },
  component: {
    devServer: {
      framework: 'react',                  // 组件测试用 React
      bundler: 'vite',                     // 打包器用 Vite
    },
  },
});

核心用法

// cypress/e2e/login.cy.ts
describe('登录功能', () => {              // describe 分组

  beforeEach(() => {                      // 每个测试前执行
    cy.visit('/login');                   // 打开登录页
  });

  it('成功登录后跳转 dashboard', () => {
    // ── 操作元素 ──
    cy.get('[name=username]').type('testuser');  // 输入用户名
    cy.get('[name=password]').type('password');  // 输入密码
    cy.get('button[type=submit]').click();        // 点击提交

    // ── 断言(Cypress 自动重试直到通过或超时)──
    cy.url().should('include', '/dashboard');     // URL 应包含 /dashboard
    cy.contains('h1', '欢迎回来').should('be.visible');  // 标题可见
  });

  it('密码错误时显示错误提示', () => {
    cy.get('[name=username]').type('testuser');
    cy.get('[name=password]').type('wrong');
    cy.get('button[type=submit]').click();

    cy.get('.error-message')                     // 错误提示元素
      .should('be.visible')                       // 应该可见
      .and('contain', '用户名或密码错误');         // 应该包含此文字
  });

  it('使用 Fixture 数据测试', () => {
    cy.fixture('user.json').then(user => {       // 读取 fixtures/user.json
      cy.get('[name=username]').type(user.name); // 使用 fixture 中的数据
      cy.get('[name=password]').type(user.password);
    });
  });
});

实战案例

// ── 测试生信工具的核心流程 ──
// cypress/e2e/blast-search.cy.ts

describe('BLAST 序列比对', () => {

  beforeEach(() => {
    // 拦截 API 请求,避免真实调用(加快测试速度)
    cy.intercept('POST', '/api/blast', {
      statusCode: 200,
      body: {
        hits: [
          { accession: 'NM_000546', score: 98.5, evalue: '1e-120', description: 'TP53' },
          { accession: 'NM_007294', score: 95.1, evalue: '2e-98', description: 'BRCA1' },
        ],
        queryLength: 500,
      },
    }).as('blastRequest');                       // 给拦截起个别名

    cy.visit('/blast');                          // 打开 BLAST 页面
  });

  it('提交序列并显示比对结果', () => {
    const testSeq = 'ATGCGATCGTAGCTAGCTAGCTAG';  // 测试序列

    // 输入序列
    cy.get('[data-cy=sequence-input]').type(testSeq);

    // 选择数据库
    cy.get('[data-cy=database-select]').select('nr');  // 选择 nr 数据库

    // 点击运行
    cy.get('[data-cy=run-blast]').click();

    // 等待 API 请求完成
    cy.wait('@blastRequest');                    // 等待别名为 blastRequest 的请求

    // 验证结果表格
    cy.get('[data-cy=result-table] tbody tr')    // 表格行
      .should('have.length', 2);                 // 应有2行结果

    cy.get('[data-cy=result-table]')
      .contains('td', 'TP53')                   // 表格中应包含 TP53
      .should('be.visible');
  });

  it('验证 E-value 过滤功能', () => {
    cy.get('[data-cy=evalue-threshold]').clear().type('1e-100');  // 设置阈值
    cy.get('[data-cy=apply-filter]').click();

    // 只有 evalue < 1e-100 的结果(TP53)应显示
    cy.get('[data-cy=result-table] tbody tr').should('have.length', 1);
  });
});

// cypress/support/commands.ts — 自定义命令
Cypress.Commands.add('login', (username: string, password: string) => {
  cy.session([username, password], () => {      // session 缓存登录状态
    cy.visit('/login');
    cy.get('[name=username]').type(username);
    cy.get('[name=password]').type(password);
    cy.get('button[type=submit]').click();
    cy.url().should('include', '/dashboard');
  });
});

// 使用自定义命令
cy.login('admin', 'password');                  // 一行完成登录

常见报错与解决

报错原因解决方法
cy.get() timed out元素不存在或未出现检查选择器,增加 { timeout: 15000 }
cy.visit() failed应用未启动确认 baseUrl 服务已运行
CORS error跨域请求未处理cypress.config 中设置 chromeWebSecurity: false
测试间状态污染beforeEach 未清理状态beforeEach 中重置数据或清除 cookies
CI 中视频录制慢视频编码消耗 CPU设置 video: false 关闭录制

速查表

# 运行命令
npx cypress open         # 开启 Cypress 图形界面(开发调试用)
npx cypress run          # 无头运行(CI 用)
npx cypress run --spec "cypress/e2e/login.cy.ts"  # 只运行指定文件
npx cypress run --browser firefox  # 指定浏览器

# 常用 Cypress 命令
cy.visit(url)            # 打开页面
cy.get(selector)         # 选择元素
cy.contains(text)        # 按文字查找元素
cy.click()               # 点击
cy.type(text)            # 输入文字
cy.should(assertion)     # 断言
cy.wait(alias)           # 等待 API 请求
cy.intercept(method, url, response)  # 拦截请求
cy.fixture(filename)     # 读取测试数据
cy.screenshot()          # 手动截图

# 官方文档:https://docs.cypress.io/
# vs Playwright 对比:Cypress 调试更直观,Playwright 多浏览器更强