断言
你将学习到
- 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.focused expect($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-chaiSinon.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')