Cypress 基础测试 #
测试的基本结构 #
describe 和 it #
Cypress 使用 Mocha 的 BDD 语法来组织测试:
javascript
describe('测试套件名称', () => {
it('测试用例名称', () => {
// 测试代码
});
});
第一个测试 #
javascript
// cypress/e2e/login.cy.js
describe('登录功能', () => {
it('用户可以使用正确的凭据登录', () => {
cy.visit('/login');
cy.get('#username').type('testuser');
cy.get('#password').type('password123');
cy.get('button[type="submit"]').click();
cy.get('.welcome-message').should('contain', '欢迎回来');
});
});
context 块 #
context 是 describe 的别名,用于更清晰地表达测试上下文:
javascript
describe('购物车功能', () => {
context('当购物车为空时', () => {
it('应该显示空购物车提示', () => {
cy.visit('/cart');
cy.get('.empty-cart').should('be.visible');
});
});
context('当购物车有商品时', () => {
it('应该显示商品列表', () => {
cy.visit('/cart');
cy.get('.cart-items').should('have.length.gt', 0);
});
});
});
测试生命周期 #
钩子函数 #
javascript
describe('用户管理', () => {
// 所有测试之前执行一次
before(() => {
cy.log('初始化测试环境');
cy.request('POST', '/api/test/setup');
});
// 所有测试之后执行一次
after(() => {
cy.log('清理测试环境');
cy.request('POST', '/api/test/teardown');
});
// 每个测试之前执行
beforeEach(() => {
cy.log('每个测试前的准备');
cy.visit('/dashboard');
});
// 每个测试之后执行
afterEach(() => {
cy.log('每个测试后的清理');
cy.clearCookies();
cy.clearLocalStorage();
});
it('测试1', () => {
cy.get('.dashboard').should('exist');
});
it('测试2', () => {
cy.get('.sidebar').should('exist');
});
});
执行顺序 #
text
before() ─── 执行一次
│
├── beforeEach() ─── 测试1前
│ └── it('测试1')
│ └── afterEach() ─── 测试1后
│
├── beforeEach() ─── 测试2前
│ └── it('测试2')
│ └── afterEach() ─── 测试2后
│
after() ─── 执行一次
访问页面 #
cy.visit() #
javascript
// 访问相对路径(基于 baseUrl)
cy.visit('/login');
// 访问绝对路径
cy.visit('https://example.com/login');
// 带查询参数
cy.visit('/search?q=cypress');
// 带 options
cy.visit('/dashboard', {
timeout: 30000,
onBeforeLoad(win) {
// 页面加载前的操作
win.localStorage.setItem('token', 'test-token');
},
onLoad(win) {
// 页面加载后的操作
console.log('页面加载完成');
}
});
页面加载验证 #
javascript
describe('首页测试', () => {
it('应该成功加载首页', () => {
cy.visit('/');
// 验证标题
cy.title().should('include', 'My App');
// 验证 URL
cy.url().should('include', '/');
// 验证关键元素
cy.get('h1').should('be.visible');
});
});
测试组织 #
嵌套 describe #
javascript
describe('用户中心', () => {
describe('个人资料', () => {
describe('头像上传', () => {
it('应该支持上传 JPG 格式', () => {});
it('应该支持上传 PNG 格式', () => {});
it('应该拒绝不支持的格式', () => {});
});
describe('信息修改', () => {
it('应该允许修改用户名', () => {});
it('应该允许修改邮箱', () => {});
});
});
describe('账户设置', () => {
it('应该允许修改密码', () => {});
it('应该允许删除账户', () => {});
});
});
测试命名规范 #
javascript
// ✅ 好的命名 - 描述行为和预期结果
it('用户输入正确的用户名和密码后应该成功登录', () => {});
it('用户输入错误的密码后应该显示错误提示', () => {});
it('购物车为空时应该显示"去购物"按钮', () => {});
// ❌ 不好的命名 - 太模糊
it('测试登录', () => {});
it('测试1', () => {});
it('works', () => {});
跳过测试 #
skip 方法 #
javascript
describe('支付功能', () => {
it('应该支持信用卡支付', () => {
// 正常执行
});
// 跳过单个测试
it.skip('应该支持 PayPal 支付', () => {
// 这个测试会被跳过
});
// 跳过整个 describe
describe.skip('银行转账支付', () => {
it('应该支持银行卡支付', () => {});
});
});
条件跳过 #
javascript
describe('国际化测试', () => {
const supportedLocales = ['en', 'zh', 'ja'];
const testLocale = Cypress.env('locale') || 'en';
it('应该正确显示翻译文本', () => {
if (!supportedLocales.includes(testLocale)) {
this.skip();
}
cy.visit(`/${testLocale}`);
cy.get('.translated-text').should('exist');
});
});
仅运行特定测试 #
only 方法 #
javascript
describe('用户模块', () => {
it('测试A', () => {
// 不会执行
});
// 只运行这个测试
it.only('测试B - 正在调试', () => {
cy.visit('/');
cy.get('.element').click();
});
it('测试C', () => {
// 不会执行
});
// 只运行这个 describe
describe.only('登录功能', () => {
it('登录测试', () => {
// 会执行
});
});
});
测试模式 #
AAA 模式 #
Arrange(准备)- Act(执行)- Assert(断言):
javascript
it('应该成功添加商品到购物车', () => {
// Arrange - 准备测试数据
const product = { id: 1, name: '商品A', price: 100 };
// Act - 执行操作
cy.visit('/products');
cy.get(`[data-product-id="${product.id}"]`).click();
cy.get('.add-to-cart').click();
// Assert - 验证结果
cy.get('.cart-count').should('contain', '1');
cy.get('.cart-total').should('contain', '¥100');
});
Given-When-Then 模式 #
javascript
describe('用户登录', () => {
it('用户使用正确凭据登录成功', () => {
// Given - 前置条件
cy.visit('/login');
const user = { username: 'testuser', password: 'password123' };
// When - 执行动作
cy.get('#username').type(user.username);
cy.get('#password').type(user.password);
cy.get('button[type="submit"]').click();
// Then - 验证结果
cy.url().should('include', '/dashboard');
cy.get('.user-name').should('contain', user.username);
});
});
测试数据 #
使用变量 #
javascript
describe('表单测试', () => {
// 在 describe 作用域定义测试数据
const formData = {
name: '张三',
email: 'zhangsan@example.com',
phone: '13800138000'
};
it('应该正确提交表单', () => {
cy.visit('/contact');
cy.get('#name').type(formData.name);
cy.get('#email').type(formData.email);
cy.get('#phone').type(formData.phone);
cy.get('form').submit();
cy.get('.success-message').should('be.visible');
});
});
使用别名 #
javascript
describe('API 数据测试', () => {
beforeEach(() => {
// 使用别名存储数据
cy.request('GET', '/api/users/1').as('userResponse');
});
it('应该显示用户信息', function() {
// 使用 this 访问别名
const user = this.userResponse.body;
cy.visit(`/users/${user.id}`);
cy.get('.user-name').should('contain', user.name);
});
});
重试机制 #
自动重试 #
Cypress 会自动重试断言:
javascript
it('等待元素出现', () => {
cy.visit('/');
// 自动重试直到元素出现或超时
cy.get('.loading').should('not.exist');
cy.get('.data-loaded').should('be.visible');
});
自定义重试 #
javascript
// 自定义重试逻辑
it('自定义重试示例', () => {
cy.visit('/');
cy.get('.status', { timeout: 10000 }) // 10秒超时
.should('have.class', 'active');
});
// 使用 retry 选项
Cypress.Commands.add('waitForCondition', (condition, options = {}) => {
const timeout = options.timeout || 5000;
const interval = options.interval || 100;
return cy.wrap(null, { timeout }).should(() => {
expect(condition()).to.be.true;
});
});
调试技巧 #
cy.pause() #
javascript
it('调试测试', () => {
cy.visit('/login');
cy.get('#username').type('testuser');
cy.pause(); // 执行到这里会暂停
cy.get('#password').type('password');
cy.get('button').click();
});
cy.debug() #
javascript
it('调试示例', () => {
cy.visit('/');
cy.get('.item').then(($item) => {
cy.debug(); // 打开开发者工具调试
console.log($item.text());
});
});
cy.log() #
javascript
it('日志输出', () => {
cy.visit('/');
cy.log('开始测试');
cy.get('.button').click();
cy.log('按钮已点击');
cy.get('.result').then(($el) => {
cy.log(`结果: ${$el.text()}`);
});
});
测试文件组织 #
按功能模块组织 #
text
cypress/e2e/
├── auth/
│ ├── login.cy.js
│ ├── register.cy.js
│ └── logout.cy.js
├── products/
│ ├── list.cy.js
│ ├── detail.cy.js
│ └── search.cy.js
└── checkout/
├── cart.cy.js
└── payment.cy.js
按用户流程组织 #
text
cypress/e2e/
├── user-journey/
│ ├── guest-browsing.cy.js
│ ├── user-registration.cy.js
│ ├── purchase-flow.cy.js
│ └── support-ticket.cy.js
└── admin/
├── user-management.cy.js
└── product-management.cy.js
完整示例 #
登录功能测试 #
javascript
// cypress/e2e/auth/login.cy.js
describe('登录功能', () => {
const user = {
username: 'testuser',
password: 'Password123!'
};
beforeEach(() => {
cy.visit('/login');
});
context('表单验证', () => {
it('应该显示登录表单', () => {
cy.get('#username').should('be.visible');
cy.get('#password').should('be.visible');
cy.get('button[type="submit"]').should('be.visible');
});
it('空表单提交应该显示错误', () => {
cy.get('button[type="submit"]').click();
cy.get('.error-message').should('contain', '请填写用户名');
});
it('无效邮箱格式应该显示错误', () => {
cy.get('#username').type('invalid-email');
cy.get('#password').type('password');
cy.get('button[type="submit"]').click();
cy.get('.error-message').should('contain', '邮箱格式不正确');
});
});
context('登录流程', () => {
it('正确的凭据应该登录成功', () => {
cy.get('#username').type(user.username);
cy.get('#password').type(user.password);
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
cy.get('.welcome-message').should('contain', '欢迎');
});
it('错误的密码应该显示错误提示', () => {
cy.get('#username').type(user.username);
cy.get('#password').type('wrongpassword');
cy.get('button[type="submit"]').click();
cy.get('.error-message').should('contain', '用户名或密码错误');
});
it('记住登录状态', () => {
cy.get('#username').type(user.username);
cy.get('#password').type(user.password);
cy.get('#remember-me').check();
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
cy.getCookie('remember_token').should('exist');
});
});
context('登录后操作', () => {
beforeEach(() => {
cy.login(user.username, user.password);
});
it('应该能够访问受保护的页面', () => {
cy.visit('/profile');
cy.get('.profile-page').should('be.visible');
});
it('应该能够成功登出', () => {
cy.get('.logout-button').click();
cy.url().should('include', '/login');
});
});
});
运行测试 #
交互模式 #
bash
# 打开 Cypress Test Runner
npx cypress open
# 选择测试文件运行
# 可以看到实时执行过程
命令行模式 #
bash
# 运行所有测试
npx cypress run
# 运行指定文件
npx cypress run --spec "cypress/e2e/login.cy.js"
# 运行匹配的文件
npx cypress run --spec "cypress/e2e/auth/**/*.cy.js"
# 指定浏览器
npx cypress run --browser chrome
# 无头模式
npx cypress run --headless
下一步 #
现在你已经掌握了 Cypress 基础测试的编写方法,接下来学习 选择器 了解更多元素定位方式!
最后更新:2026-03-28