断言
你将学习到
- Cypress支持的Chai、Chai-jQuery和Sinon-Chai断言
- 如何为常见用例编写断言
- 如何链式组合断言
Cypress内置了流行的Chai断言库,以及针对Sinon和jQuery的扩展,为您提供了数十种强大的断言功能。
如果想了解如何使用这些断言,请阅读Cypress入门指南中的断言部分。
Chai
https://github.com/chaijs/chai以下链式断言可用于BDD风格断言(expect/should)。列出的别名可与原始链式断言互换使用。完整BDD Chai断言列表参见此处。
| 断言链 | 示例 |
|---|---|
| not | .should('not.equal', 'Jane')expect(name).to.not.equal('Jane') |
| deep | .should('deep.equal', { name: 'Jane' })expect(obj).to.deep.equal({ name: 'Jane' }) |
| nested | .should('have.nested.property', 'a.b[1]').should('nested.include', {'a.b[1]': 'y'})expect({a: {b: 'x'}}).to.have.nested.property('a.b')expect({a: {b: 'x'}}).to.nested.include({'a.b': 'x'}) |
| ordered | .should('have.ordered.members', [1, 2])expect([1, 2]).to.have.ordered.members([1, 2])expect([1, 2]).not.to.have.ordered.members([2, 1]) |
| any | .should('have.any.keys', 'age')expect(arr).to.have.any.keys('age') |
| all | .should('have.all.keys', 'name', 'age')expect(arr).to.have.all.keys('name', 'age') |
| a(类型) 别名: an | .should('be.a', 'string')expect('test').to.be.a('string') |
| include(值) 别名: contain, includes, contains | .should('include', 2)expect([1,2,3]).to.include(2) |
| ok | .should('not.be.ok')expect(undefined).to.not.be.ok |
| true | .should('be.true')expect(true).to.be.true |
| false | .should('be.false')expect(false).to.be.false |
| null | .should('be.null')expect(null).to.be.null |
| undefined | .should('be.undefined')expect(undefined).to.be.undefined |
| exist | .should('exist')expect(myVar).to.exist |
| empty | .should('be.empty')expect([]).to.be.empty |
| arguments 别名: Arguments | .should('be.arguments')expect(arguments).to.be.arguments |
| equal(值) 别名: equals, eq | .should('equal', 42)expect(42).to.equal(42) |
| deep.equal(值) | .should('deep.equal', { name: 'Jane' })expect({ name: 'Jane' }).to.deep.equal({ name: 'Jane' }) |
| eql(值) 别名: eqls | .should('eql', { name: 'Jane' })expect({ name: 'Jane' }).to.eql({ name: 'Jane' }) |
| greaterThan(值) 别名: gt, above | .should('be.greaterThan', 5)expect(10).to.be.greaterThan(5) |
| least(值) 别名: gte | .should('be.at.least', 10)expect(10).to.be.at.least(10) |
| lessThan(值) 别名: lt, below | .should('be.lessThan', 10)expect(5).to.be.lessThan(10) |
| most(值) 别名: lte | .should('have.length.of.at.most', 4)expect('test').to.have.length.of.at.most(4) |
| within(起始, 结束) | .should('be.within', 5, 10)expect(7).to.be.within(5, 10) |
| instanceOf(构造函数) 别名: instanceof | .should('be.instanceOf', Array)expect([1, 2, 3]).to.be.instanceOf(Array) |
| property(名称, [值]) | .should('have.property', 'name')expect(obj).to.have.property('name') |
| deep.property(名称, [值]) | .should('have.deep.property', 'tests[1]', 'e2e')expect(deepObj).to.have.deep.property('tests[1]', 'e2e') |
| ownProperty(名称) 别名: haveOwnProperty, own.property | .should('have.ownProperty', 'length')expect('test').to.have.ownProperty('length') |
| ownPropertyDescriptor(名称) 别名: haveOwnPropertyDescriptor | .should('have.ownPropertyDescriptor', 'a')expect({a: 1}).to.have.ownPropertyDescriptor('a') |
| lengthOf(值) | .should('have.lengthOf', 4)expect('test').to.have.lengthOf(4) |
| match(正则表达式) 别名: matches | .should('to.match', /^test/)expect('testing').to.match(/^test/) |
| string(字符串) | .should('have.string', 'test')expect('testing').to.have.string('test') |
| keys(键1, [键2], [...]) 别名: key | .should('have.keys', 'pass', 'fail')expect({ pass: 1, fail: 2 }).to.have.keys('pass', 'fail') |
| throw(构造函数) 别名: throws, Throw | .should('throw', Error)expect(fn).to.throw(Error) |
| respondTo(方法) 别名: respondsTo | .should('respondTo', 'getName')expect(obj).to.respondTo('getName') |
| itself | .should('itself.respondTo', 'getName')expect(Foo).itself.to.respondTo('bar') |
| satisfy(方法) 别名: satisfies | .should('satisfy', (num) => num > 0)expect(1).to.satisfy((num) => num > 0) |
| closeTo(期望值, 误差范围) 别名: approximately | .should('be.closeTo', 1, 0.5)expect(1.5).to.be.closeTo(1, 0.5) |
| members(集合) | .should('include.members', [3, 2])expect([1, 2, 3]).to.include.members([3, 2]) |
| oneOf(值列表) | .should('be.oneOf', [1, 2, 3])expect(2).to.be.oneOf([1,2,3]) |
| change(函数) 别名: changes | .should('change', obj, 'val')expect(fn).to.change(obj, 'val') |
| increase(函数) 别名: increases | .should('increase', obj, 'val')expect(fn).to.increase(obj, 'val') |
| decrease(函数) 别名: decreases | .should('decrease', obj, 'val')expect(fn).to.decrease(obj, 'val') |
以下获取器也可用于BDD断言。它们本身不执行任何操作,但能让您编写更清晰的英语句子。
| 可链式获取器 |
|---|
to, be, been, is, that, which, and, has, have, with, at, of, same |
Chai-jQuery
https://github.com/chaijs/chai-jquery这些链式断言用于DOM对象断言。
通常在使用了DOM命令如:cy.get()、cy.contains()等后使用这些断言。
| 断言链 | 断言示例 |
|---|---|
| attr(名称, [值]) | .should('have.attr', 'bar')expect($el).to.have.attr('foo', 'bar') |
| prop(名称, [值]) | .should('have.prop', 'disabled', false)expect($el).to.have.prop('disabled', false) |
| css(名称, [值]) | .should('have.css', 'background-color', 'rgb(0, 0, 0)')expect($el).to.have.css('background-color', 'rgb(0, 0, 0)') |
| data(名称, [值]) | .should('have.data', 'foo', 'bar')expect($el).to.have.data('foo', 'bar') |
| class(类名) | .should('have.class', 'foo')expect($el).to.have.class('foo') |
| id(ID) | .should('have.id', 'foo')expect($el).to.have.id('foo') |
| html(HTML) | .should('have.html', 'I love testing')expect($el).to.have.html('with Cypress') |
| text(文本) | .should('have.text', 'I love testing')expect($el).to.have.text('with Cypress') |
| value(值) | .should('have.value', 'test@dev.com')expect($el).to.have.value('test@dev.com') |
| visible | .should('be.visible')expect($el).to.be.visible |
| hidden | .should('be.hidden')expect($el).to.be.hidden |
| selected | .should('be.selected')expect($option).not.to.be.selected |
| checked | .should('be.checked')expect($input).not.to.be.checked |
| focus[ed] | .should('have.focus')expect($input).not.to.be.focusedexpect($input).to.have.focus |
| enabled | .should('be.enabled')expect($input).to.be.enabled |
| disabled | .should('be.disabled')expect($input).to.be.disabled |
| empty | .should('be.empty')expect($el).not.to.be.empty |
| exist | .should('exist')expect($nonexistent).not.to.exist |
| match(选择器) | .should('match', ':empty')expect($emptyEl).to.match(':empty') |
| contain(文本) | .should('contain', 'text')expect($el).to.contain('text') |
| descendants(选择器) | .should('have.descendants', 'div')expect($el).to.have.descendants('div') |
Sinon-Chai
https://github.com/domenic/sinon-chai| Sinon.JS属性/方法 | 断言示例 |
|---|---|
| called | .should('have.been.called')expect(spy).to.be.called |
| callCount | .should('have.callCount', 3)expect(spy).to.have.callCount(n) |
| calledOnce | .should('have.been.calledOnce')expect(spy).to.be.calledOnce |
| calledTwice | .should('have.been.calledTwice')expect(spy).to.be.calledTwice |
| calledThrice | .should('have.been.calledThrice')expect(spy).to.be.calledThrice |
| calledBefore | .should('have.been.calledBefore', spy2)expect(spy1).to.be.calledBefore(spy2) |
| calledAfter | .should('have.been.calledAfter', spy2)expect(spy1).to.be.calledAfter(spy2) |
| calledWithNew | .should('have.been.calledWithNew')expect(spy).to.be.calledWithNew |
| alwaysCalledWithNew | .should('have.always.been.calledWithNew')expect(spy).to.always.be.calledWithNew |
| calledOn | .should('have.been.calledOn', context)expect(spy).to.be.calledOn(context) |
| alwaysCalledOn | .should('have.always.been.calledOn', context)expect(spy).to.always.be.calledOn(context) |
| calledWith | .should('have.been.calledWith', ...args)expect(spy).to.be.calledWith(...args) |
| alwaysCalledWith | .should('have.always.been.calledWith', ...args)expect(spy).to.always.be.calledWith(...args) |
| calledOnceWith | .should('have.been.calledOnceWith', ...args)expect(spy).to.be.calledOnceWith(...args) |
| calledWithExactly | .should('have.been.calledWithExactly', ...args)expect(spy).to.be.calledWithExactly(...args) |
| alwaysCalledWithExactly | .should('have.always.been.calledWithExactly', ...args)expect(spy).to.always.be.calledWithExactly(...args) |
| calledOnceWithExactly | .should('have.been.calledOnceWithExactly', ...args)expect(spy).to.be.calledOnceWithExactly(...args) |
| calledWithMatch | .should('have.been.calledWithMatch',...args)expect(spy).to.be.calledWithMatch(...args) |
| alwaysCalledWithMatch | .should('have.always.been.calledWithMatch',...args)expect(spy).to.always.be.calledWithMatch(...args) |
| returned | .should('have.returned', 'foo')expect(spy).to.have.returned(returnVal) |
| alwaysReturned | .should('have.always.returned', 'foo')expect(spy).to.have.always.returned(returnVal) |
| threw | .should('have.thrown', TypeError)expect(spy).to.have.thrown(errorObjOrErrorTypeStringOrNothing) |
| alwaysThrew | .should('have.always.thrown', 'TypeError')expect(spy).to.have.always.thrown(errorObjOrErrorTypeStringOrNothing) |
添加新断言
由于我们使用chai,这意味着您可以按需扩展它。Cypress会自动兼容添加到chai的新断言。您可以:
常见断言
以下是常见的元素断言列表。注意我们如何将上述断言与.should()结合使用。您可能还想阅读Cypress如何重试断言。
长度
// 重试直到找到3个匹配的<li.selected>
cy.get('li.selected').should('have.length', 3)
类名
// 重试直到此输入没有disabled类
cy.get('form').find('input').should('not.have.class', 'disabled')
值
// 重试直到此文本区域有正确的值
cy.get('textarea').should('have.value', 'foo bar baz')
文本内容
// 断言元素的文本内容完全匹配给定文本
cy.get('[data-testid="user-name"]').should('have.text', 'Joe Smith')
// 断言元素的文本包含给定子字符串
cy.get('[data-testid="address"]').should('include.text', 'Atlanta')
// 重试直到此span不包含'click me'
cy.get('a').parent('span.help').should('not.contain', 'click me')
// 元素的文本应以"Hello"开头
cy.get('[data-testid="greeting"]')
.invoke('text')
.should('match', /^Hello/)
// 使用cy.contains通过文本查找元素
// 匹配给定的正则表达式
cy.contains('[data-testid="greeting"]', /^Hello/)
**提示:**关于包含不间断空格实体的文本断言,请阅读如何获取元素的文本内容?
可见性
// 重试直到data-testid为"form-submit"的元素可见
cy.get('[data-testid="form-submit"]').should('be.visible')
// 重试直到文本为"write tests"的列表项可见
cy.contains('[data-testid="todo"] li', 'write tests').should('be.visible')
**注意:**如果存在多个元素,断言be.visible和not.be.visible的行为不同:
// 重试直到某些元素可见
cy.get('li').should('be.visible')
// 重试直到所有元素不可见
cy.get('li.hidden').should('not.be.visible')
观看短视频"多个元素与should('be.visible')断言",了解如何正确检查元素的可见性。
存在性
// 重试直到加载旋转器不存在
cy.get('[data-testid="loading"]').should('not.exist')
状态
// 重试直到单选按钮被选中
cy.get(':radio').should('be.checked')
CSS
// 重试直到元素有匹配的CSS
cy.get('[data-testid="completed"]').should(
'have.css',
'text-decoration',
'line-through'
)
// 重试直到手风琴CSS有"display: none"属性
cy.get('[data-testid="accordion"]').should('not.have.css', 'display', 'none')
禁用属性
<input type="text" data-testid="example-input" disabled />
cy.get('[data-testid="example-input"]')
.should('be.disabled')
// 从测试中启用此元素
.invoke('prop', 'disabled', false)
cy.get('[data-testid="example-input"]')
// 可以使用"enabled"断言
.should('be.enabled')
// 或否定"disabled"断言
.and('not.be.disabled')
否定断言
断言分为肯定和否定两种。肯定断言的例子:
cy.get('[data-testid="todo-item"]')
.should('have.length', 2)
.and('have.class', 'completed')
否定断言在断言前加上"not"链。否定断言的例子:
cy.contains('first todo').should('not.have.class', 'completed')
cy.get('[data-testid="loading"]').should('not.be.visible')
误通过的测试
否定断言可能会因意外原因通过。假设我们要测试一个Todo列表应用在输入Todo并按下回车后是否添加了新Todo项。
肯定断言
当向列表添加元素并使用肯定断言时,测试断言应用中特定数量的Todo项。
如果应用行为异常,比如添加空白Todo而不是包含文本"Write tests"的新Todo,下面的测试可能仍会错误地通过。
cy.get('[data-testid="todos"]').should('have.length', 2)
cy.get('[data-testid="new-todo"]').type('Write tests{enter}')
// 使用肯定断言检查确切的项目数量
cy.get('[data-testid="todos"]').should('have.length', 3)
否定断言
但在下面的测试中使用否定断言时,当应用以多种意外方式行为时,测试可能会错误地通过:
- 应用删除了整个Todo列表而 不是插入第3个Todo
- 应用删除了一个Todo而不是添加新Todo
- 应用添加了空白Todo
cy.get('[data-testid="todos"]').should('have.length', 2)
cy.get('[data-testid="new-todo"]').type('Write tests{enter}')
// 使用否定断言检查不是某个数量的项目
cy.get('[data-testid="todos"]').should('not.have.length', 2)
Should回调
如果内置断言不够用,您可以编写自己的断言函数并将其作为回调传递给.should()命令。Cypress会自动重试回调函数直到通过或命令超时。参见.should()文档。
<div class="main-abc123 heading-xyz987">Introduction</div>
cy.get('div').should(($div) => {
expect($div).to.have.length(1)
const className = $div[0].className
// className将是类似"main-abc123 heading-xyz987"的字符串
expect(className).to.match(/heading-/)
})
多重断言
您可以在同一命令上附加多个断言。
<a
data-testid="assertions-link"
class="active"
href="https://on.cypress.io"
target="_blank"
>
Cypress Docs
</a>
cy.get('[data-testid="assertions-link"]')
.should('have.class', 'active')
.and('have.attr', 'href')
.and('include', 'cypress.io')
注意所有链式断言都使用对原始主题的相同引用。例如,如果要测试一个先出现后消失的加载元素,以下方法不适用,因为同一元素不能同时可见和不可见:
// ⛔️ 不适用
cy.get('[data-testid="loading"]').should('be.visible').and('not.be.visible')
正确的做法是拆分断言并重新查询元素:
// ✅ 正确方法
cy.get('[data-testid="loading"]').should('be.visible')
cy.get('[data-testid="loading"]').should('not.be.visible')