Cypress 断言 #
断言基础 #
两种断言方式 #
Cypress 支持两种断言方式:
javascript
// 方式一:隐式断言(推荐)
cy.get('.title').should('contain', '欢迎');
// 方式二:显式断言
cy.get('.title').then(($el) => {
expect($el.text()).to.contain('欢迎');
});
should() 方法 #
should() 是 Cypress 推荐的断言方式,支持链式调用:
javascript
cy.get('.status')
.should('be.visible')
.and('contain', '成功')
.and('have.class', 'success');
expect() 方法 #
expect() 来自 Chai 断言库,用于更复杂的断言:
javascript
cy.get('.items').then(($items) => {
expect($items).to.have.length(5);
expect($items.first()).to.contain.text('Item 1');
});
常用断言 #
存在性断言 #
javascript
// 元素存在
cy.get('.element').should('exist');
cy.get('.element').should('not.exist');
// 元素可见
cy.get('.element').should('be.visible');
cy.get('.element').should('not.be.visible');
// DOM 包含
cy.get('.container').should('contain', '文本');
cy.get('.container').should('contain.text', '文本');
cy.get('.container').should('contain.html', '<span>');
文本断言 #
javascript
// 包含文本
cy.get('.message').should('contain', '欢迎');
cy.get('.message').should('include.text', '欢迎');
// 精确文本
cy.get('.message').should('have.text', '欢迎回来');
cy.get('.message').should('have.text', '欢迎\n回来'); // 保留空白
// 匹配文本
cy.get('.message').should('match', /^欢迎/);
cy.get('.message').invoke('text').should('match', /\d+/);
// 不包含文本
cy.get('.message').should('not.contain', '错误');
属性断言 #
javascript
// 有某个属性
cy.get('input').should('have.attr', 'placeholder');
// 属性值
cy.get('input').should('have.attr', 'type', 'email');
cy.get('a').should('have.attr', 'href', '/login');
// data 属性
cy.get('.item').should('have.data', 'id', '123');
// prop 属性
cy.get('input').should('have.prop', 'checked', true);
cy.get('input').should('have.prop', 'disabled', false);
类名断言 #
javascript
// 有某个类
cy.get('.button').should('have.class', 'active');
cy.get('.button').should('not.have.class', 'disabled');
// 多个类
cy.get('.button').should('have.class', 'btn').and('have.class', 'btn-primary');
值断言 #
javascript
// 输入值
cy.get('input').should('have.value', 'test@example.com');
cy.get('input').should('not.have.value', '');
// select 值
cy.get('select').should('have.value', 'option1');
// textarea 值
cy.get('textarea').should('have.value', '内容文本');
数量断言 #
javascript
// 元素数量
cy.get('.item').should('have.length', 5);
cy.get('.item').should('have.length.gt', 0); // 大于 0
cy.get('.item').should('have.length.gte', 1); // 大于等于 1
cy.get('.item').should('have.length.lt', 10); // 小于 10
cy.get('.item').should('have.length.lte', 9); // 小于等于 9
cy.get('.item').should('have.length.within', 1, 10); // 在 1-10 之间
状态断言 #
javascript
// 禁用状态
cy.get('button').should('be.disabled');
cy.get('button').should('not.be.disabled');
// 选中状态
cy.get('input[type="checkbox"]').should('be.checked');
cy.get('input[type="radio"]').should('be.checked');
// 聚焦状态
cy.get('input').should('be.focused');
cy.get('input').should('not.be.focused');
// 只读状态
cy.get('input').should('be.readonly');
// 必填状态
cy.get('input').should('be.required');
CSS 断言 #
javascript
// CSS 属性
cy.get('.box').should('have.css', 'display', 'block');
cy.get('.box').should('have.css', 'background-color', 'rgb(0, 128, 0)');
// CSS 类
cy.get('.element').should('have.class', 'hidden');
// 计算样式
cy.get('.element').should('have.css', 'opacity', '1');
样式断言 #
javascript
// 宽高
cy.get('.box').should('have.css', 'width', '100px');
cy.get('.box').should('have.css', 'height', '50px');
// 位置
cy.get('.box').should('have.css', 'position', 'absolute');
比较断言 #
相等断言 #
javascript
// 等于
cy.wrap(1).should('eq', 1);
cy.wrap('hello').should('eq', 'hello');
// 深度相等
cy.wrap({ a: 1 }).should('deep.equal', { a: 1 });
cy.wrap([1, 2, 3]).should('deep.equal', [1, 2, 3]);
// 近似相等
cy.wrap(1.001).should('be.closeTo', 1, 0.01);
大小比较 #
javascript
// 大于
cy.wrap(5).should('be.gt', 3);
cy.wrap(5).should('be.greaterThan', 3);
// 大于等于
cy.wrap(5).should('be.gte', 5);
cy.wrap(5).should('be.at.least', 5);
// 小于
cy.wrap(3).should('be.lt', 5);
cy.wrap(3).should('be.lessThan', 5);
// 小于等于
cy.wrap(5).should('be.lte', 5);
cy.wrap(5).should('be.at.most', 5);
// 范围
cy.wrap(5).should('be.within', 1, 10);
类型断言 #
javascript
// 类型检查
cy.wrap('hello').should('be.a', 'string');
cy.wrap(123).should('be.a', 'number');
cy.wrap({}).should('be.an', 'object');
cy.wrap([]).should('be.an', 'array');
cy.wrap(true).should('be.a', 'boolean');
// instanceof
cy.wrap(new Date()).should('be.instanceof', Date);
真值断言 #
javascript
// 真值
cy.wrap(true).should('be.true');
cy.wrap(false).should('be.false');
cy.wrap(1).should('be.ok');
cy.wrap(0).should('not.be.ok');
// null 和 undefined
cy.wrap(null).should('be.null');
cy.wrap(undefined).should('be.undefined');
cy.wrap(null).should('not.be.undefined');
数组断言 #
数组内容 #
javascript
// 数组长度
cy.wrap([1, 2, 3]).should('have.length', 3);
// 包含元素
cy.wrap([1, 2, 3]).should('include', 2);
cy.wrap([1, 2, 3]).should('contain', 2);
// 包含子集
cy.wrap([1, 2, 3, 4]).should('include.members', [2, 3]);
// 完全匹配
cy.wrap([1, 2, 3]).should('have.members', [1, 2, 3]);
// 有序匹配
cy.wrap([1, 2, 3]).should('have.ordered.members', [1, 2, 3]);
// 空数组
cy.wrap([]).should('be.empty');
cy.wrap([1]).should('not.be.empty');
数组元素断言 #
javascript
cy.get('.item').then(($items) => {
const texts = $items.map((i, el) => el.textContent).get();
expect(texts).to.include('Item 1');
expect(texts).to.have.length(5);
expect(texts).to.be.an('array');
});
对象断言 #
对象属性 #
javascript
// 有某个属性
cy.wrap({ name: 'John' }).should('have.property', 'name');
// 属性值
cy.wrap({ name: 'John' }).should('have.property', 'name', 'John');
// 嵌套属性
cy.wrap({ user: { name: 'John' } }).should('have.nested.property', 'user.name', 'John');
// 深度相等
cy.wrap({ a: 1, b: 2 }).should('deep.equal', { a: 1, b: 2 });
// 部分匹配
cy.wrap({ a: 1, b: 2, c: 3 }).should('include', { a: 1 });
对象键值 #
javascript
// 有某个键
cy.wrap({ name: 'John' }).should('have.keys', ['name']);
cy.wrap({ a: 1, b: 2 }).should('have.keys', 'a', 'b');
// 包含键
cy.wrap({ a: 1, b: 2, c: 3 }).should('contain.keys', 'a', 'b');
// 空对象
cy.wrap({}).should('be.empty');
字符串断言 #
字符串内容 #
javascript
// 包含子串
cy.wrap('hello world').should('include', 'world');
cy.wrap('hello world').should('contain', 'world');
// 开头
cy.wrap('hello world').should('start.with', 'hello');
// 结尾
cy.wrap('hello world').should('end.with', 'world');
// 正则匹配
cy.wrap('hello123').should('match', /^hello\d+$/);
// 长度
cy.wrap('hello').should('have.length', 5);
字符串大小写 #
javascript
cy.wrap('HELLO').should('match', /[A-Z]+/);
cy.wrap('hello').should('match', /[a-z]+/);
函数断言 #
函数调用 #
javascript
// 是函数
cy.wrap(() => {}).should('be.a', 'function');
// spy/stub 断言
const spy = cy.spy(console, 'log');
console.log('hello');
expect(spy).to.be.called;
expect(spy).to.be.calledWith('hello');
expect(spy).to.be.calledOnce;
否定断言 #
使用 not #
javascript
// 否定任何断言
cy.get('.element').should('not.exist');
cy.get('.element').should('not.be.visible');
cy.get('.element').should('not.have.class', 'active');
cy.get('.element').should('not.contain', '错误');
// expect 方式
expect(value).to.not.be.null;
expect(value).to.not.equal(0);
链式断言 #
多重断言 #
javascript
// 使用 should 链式
cy.get('.user-card')
.should('be.visible')
.and('have.class', 'active')
.and('contain', 'John')
.and('have.attr', 'data-id', '123');
// 使用 and 连接
cy.get('.status')
.should('have.class', 'success')
.and('contain', '完成');
条件断言 #
javascript
// 根据条件断言
cy.get('body').then(($body) => {
if ($body.find('.modal').length) {
cy.get('.modal').should('be.visible');
} else {
cy.get('.content').should('be.visible');
}
});
自定义断言 #
添加自定义断言 #
javascript
// cypress/support/e2e.js
chai.Assertion.addMethod('haveUserName', function(name) {
const $el = this._obj;
const text = $el.text();
this.assert(
text.includes(name),
`expected element to have user name #{exp}, but got #{act}`,
`expected element not to have user name #{exp}`,
name,
text
);
});
// 使用
cy.get('.user-card').should('have.userName', 'John');
自定义消息 #
javascript
cy.get('.element', { timeout: 10000 })
.should(($el) => {
expect($el, '元素应该可见').to.be.visible;
expect($el.text(), '文本应该包含"成功"').to.include('成功');
});
异步断言 #
等待断言通过 #
javascript
// 自动重试直到断言通过
cy.get('.status').should('contain', '完成');
// 自定义超时
cy.get('.status', { timeout: 10000 }).should('contain', '完成');
// 多次重试
cy.get('.counter').should(($el) => {
expect(parseInt($el.text())).to.be.greaterThan(100);
});
回调断言 #
javascript
cy.get('.list').should(($list) => {
// 自定义断言逻辑
const items = $list.find('.item');
expect(items).to.have.length.at.least(1);
items.each((index, item) => {
expect(item.textContent).to.not.be.empty;
});
});
断言最佳实践 #
1. 使用语义化断言 #
javascript
// ✅ 好的做法 - 清晰表达意图
cy.get('.modal').should('be.visible');
cy.get('.button').should('be.disabled');
cy.get('.message').should('contain', '成功');
// ❌ 不好的做法 - 使用底层断言
cy.get('.modal').then(($el) => {
expect($el.css('display')).to.not.equal('none');
});
2. 合理使用链式断言 #
javascript
// ✅ 好的做法 - 相关断言链式调用
cy.get('.user-card')
.should('be.visible')
.and('contain', 'John')
.and('have.class', 'active');
// ❌ 不好的做法 - 不相关断言链式调用
cy.get('.user-card')
.should('be.visible')
.and('have.css', 'color', 'rgb(0, 0, 0)'); // 不相关
3. 避免过度断言 #
javascript
// ✅ 好的做法 - 断言关键行为
cy.get('.button').click();
cy.get('.result').should('contain', '成功');
// ❌ 不好的做法 - 断言过多细节
cy.get('.button')
.should('have.css', 'background-color', 'rgb(0, 128, 0)')
.and('have.css', 'font-size', '14px')
.click();
4. 使用有意义的断言消息 #
javascript
cy.get('.total').should(($el) => {
const total = parseFloat($el.text().replace('$', ''));
expect(total, '总价应该大于 0').to.be.greaterThan(0);
});
完整示例 #
javascript
describe('断言示例', () => {
beforeEach(() => {
cy.visit('/dashboard');
});
it('验证用户信息卡片', () => {
cy.get('.user-card')
.should('exist')
.and('be.visible')
.within(() => {
cy.get('.avatar').should('be.visible');
cy.get('.name').should('have.text', 'John Doe');
cy.get('.email').should('contain', '@example.com');
cy.get('.role').should('have.class', 'admin');
});
});
it('验证商品列表', () => {
cy.get('.product-list')
.should('exist')
.within(() => {
cy.get('.product-item')
.should('have.length.gt', 0)
.first()
.should('contain', '商品')
.and('have.attr', 'data-id');
});
});
it('验证表单状态', () => {
cy.get('form').within(() => {
cy.get('input[name="email"]')
.should('have.value', '')
.and('not.be.disabled');
cy.get('button[type="submit"]')
.should('be.disabled');
});
});
it('验证 API 响应', () => {
cy.request('GET', '/api/users').then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.be.an('array');
expect(response.body).to.have.length.gt(0);
expect(response.body[0]).to.have.property('id');
expect(response.body[0]).to.have.property('name');
});
});
it('验证复杂条件', () => {
cy.get('.status').then(($status) => {
const status = $status.text();
if (status.includes('成功')) {
cy.get('.success-icon').should('be.visible');
} else if (status.includes('失败')) {
cy.get('.error-icon').should('be.visible');
cy.get('.retry-button').should('be.visible');
}
});
});
});
下一步 #
现在你已经掌握了 Cypress 断言的使用方法,接下来学习 用户交互 了解如何模拟用户操作!
最后更新:2026-03-28