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