Assertions
What you'll learn
- Assertions available in Cypress with Chai, Chai-jQuery, and Sinon-Chai assertions
- How to write assertions for common use cases
- How to chain assertions together
Cypress bundles the popular Chai assertion library, as well as helpful extensions for Sinon and jQuery, bringing you dozens of powerful assertions for free.
If you're looking to understand how to use these assertions please read about assertions in our Introduction to Cypress guide.
Chai
https://github.com/chaijs/chaiThese chainers are available for BDD assertions (expect
/should
). Aliases
listed can be used interchangeably with their original chainer. You can see the
entire list of available BDD Chai assertions here.
Chainer | Example |
---|---|
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(type) Aliases: an | .should('be.a', 'string') expect('test').to.be.a('string') |
include(value) Aliases: 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 Aliases: Arguments | .should('be.arguments') expect(arguments).to.be.arguments |
equal(value) Aliases: equals, eq | .should('equal', 42) expect(42).to.equal(42) |
deep.equal(value) | .should('deep.equal', { name: 'Jane' }) expect({ name: 'Jane' }).to.deep.equal({ name: 'Jane' }) |
eql(value) Aliases: eqls | .should('eql', { name: 'Jane' }) expect({ name: 'Jane' }).to.eql({ name: 'Jane' }) |
greaterThan(value) Aliases: gt, above | .should('be.greaterThan', 5) expect(10).to.be.greaterThan(5) |
least(value) Aliases: gte | .should('be.at.least', 10) expect(10).to.be.at.least(10) |
lessThan(value) Aliases: lt, below | .should('be.lessThan', 10) expect(5).to.be.lessThan(10) |
most(value) Aliases: lte | .should('have.length.of.at.most', 4) expect('test').to.have.length.of.at.most(4) |
within(start, finish) | .should('be.within', 5, 10) expect(7).to.be.within(5, 10) |
instanceOf(constructor) Aliases: instanceof | .should('be.instanceOf', Array) expect([1, 2, 3]).to.be.instanceOf(Array) |
property(name, [value]) | .should('have.property', 'name') expect(obj).to.have.property('name') |
deep.property(name, [value]) | .should('have.deep.property', 'tests[1]', 'e2e') expect(deepObj).to.have.deep.property('tests[1]', 'e2e') |
ownProperty(name) Aliases: haveOwnProperty, own.property | .should('have.ownProperty', 'length') expect('test').to.have.ownProperty('length') |
ownPropertyDescriptor(name) Aliases: haveOwnPropertyDescriptor | .should('have.ownPropertyDescriptor', 'a') expect({a: 1}).to.have.ownPropertyDescriptor('a') |
lengthOf(value) | .should('have.lengthOf', 4) expect('test').to.have.lengthOf(4) |
match(RegExp) Aliases: matches | .should('to.match', /^test/) expect('testing').to.match(/^test/) |
string(string) | .should('have.string', 'test') expect('testing').to.have.string('test') |
keys(key1, [key2], [...]) Aliases: key | .should('have.keys', 'pass', 'fail') expect({ pass: 1, fail: 2 }).to.have.keys('pass', 'fail') |
throw(constructor) Aliases: throws, Throw | .should('throw', Error) expect(fn).to.throw(Error) |
respondTo(method) Aliases: respondsTo | .should('respondTo', 'getName') expect(obj).to.respondTo('getName') |
itself | .should('itself.respondTo', 'getName') expect(Foo).itself.to.respondTo('bar') |
satisfy(method) Aliases: satisfies | .should('satisfy', (num) => num > 0) expect(1).to.satisfy((num) => num > 0) |
closeTo(expected, delta) Aliases: approximately | .should('be.closeTo', 1, 0.5) expect(1.5).to.be.closeTo(1, 0.5) |
members(set) | .should('include.members', [3, 2]) expect([1, 2, 3]).to.include.members([3, 2]) |
oneOf(values) | .should('be.oneOf', [1, 2, 3]) expect(2).to.be.oneOf([1,2,3]) |
change(function) Aliases: changes | .should('change', obj, 'val') expect(fn).to.change(obj, 'val') |
increase(function) Aliases: increases | .should('increase', obj, 'val') expect(fn).to.increase(obj, 'val') |
decrease(function) Aliases: decreases | .should('decrease', obj, 'val') expect(fn).to.decrease(obj, 'val') |
These getters are also available for BDD assertions. They don't actually do anything, but they enable you to write clear, english sentences.
Chainable getters |
---|
to , be , been , is , that , which , and , has , have , with , at , of , same |
Chai-jQuery
https://github.com/chaijs/chai-jqueryThese chainers are available when asserting about a DOM object.
You will commonly use these chainers after using DOM commands like:
cy.get()
, cy.contains()
, etc.
Chainers | Assertion |
---|---|
attr(name, [value]) | .should('have.attr', 'bar') expect($el).to.have.attr('foo', 'bar') |
prop(name, [value]) | .should('have.prop', 'disabled', false) expect($el).to.have.prop('disabled', false) |
css(name, [value]) | .should('have.css', 'background-color', 'rgb(0, 0, 0)') expect($el).to.have.css('background-color', 'rgb(0, 0, 0)') |
data(name, [value]) | .should('have.data', 'foo', 'bar') expect($el).to.have.data('foo', 'bar') |
class(className) | .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(text) | .should('have.text', 'I love testing') expect($el).to.have.text('with Cypress') |
value(value) | .should('have.value', '[email protected]') expect($el).to.have.value('[email protected]') |
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(selector) | .should('match', ':empty') expect($emptyEl).to.match(':empty') |
contain(text) | .should('contain', 'text') expect($el).to.contain('text') |
descendants(selector) | .should('have.descendants', 'div') expect($el).to.have.descendants('div') |
Sinon-Chai
https://github.com/domenic/sinon-chaiThese chainers are used on assertions with cy.stub()
and
cy.spy()
.
Sinon.JS property/method | Assertion |
---|---|
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) |
Adding New Assertions
Because we are using chai
, that means you can extend it however you'd like.
Cypress will "just work" with new assertions added to chai
. You can:
- Write your own
chai
assertions as documented here. - npm install any existing
chai
library and import into your test file or support file.
Common Assertions
Here is a list of common element assertions. Notice how we use these assertions
(listed above) with .should()
. You may also want to
read about how Cypress retries
assertions.
Length
// retry until we find 3 matching <li.selected>
cy.get('li.selected').should('have.length', 3)
Class
// retry until this input does not have class disabled
cy.get('form').find('input').should('not.have.class', 'disabled')
Value
// retry until this textarea has the correct value
cy.get('textarea').should('have.value', 'foo bar baz')
Text Content
// assert the element's text content is exactly the given text
cy.get('[data-testid="user-name"]').should('have.text', 'Joe Smith')
// assert the element's text includes the given substring
cy.get('[data-testid="address"]').should('include.text', 'Atlanta')
// retry until this span does not contain 'click me'
cy.get('a').parent('span.help').should('not.contain', 'click me')
// the element's text should start with "Hello"
cy.get('[data-testid="greeting"]')
.invoke('text')
.should('match', /^Hello/)
// use cy.contains to find an element with its text
// matching the given regular expression
cy.contains('[data-testid="greeting"]', /^Hello/)
Tip: read about assertions against text with non-breaking space entities in How do I get an element's text contents?
Visibility
// retry until the element with
// data-testid "form-submit" is visible
cy.get('[data-testid="form-submit"]').should('be.visible')
// retry until the list item with
// text "write tests" is visible
cy.contains('[data-testid="todo"] li', 'write tests').should('be.visible')
Note: if there are multiple elements, the assertions be.visible
and
not.be.visible
act differently:
// retry until SOME elements are visible
cy.get('li').should('be.visible')
// retry until EVERY element is invisible
cy.get('li.hidden').should('not.be.visible')
Watch the short video "Multiple elements and should('be.visible') assertion" that shows how to correctly check the visibility of elements.
Existence
// retry until loading spinner no longer exists
cy.get('[data-testid="loading"]').should('not.exist')
State
// retry until our radio is checked
cy.get(':radio').should('be.checked')
CSS
// retry until element has matching css
cy.get('[data-testid="completed"]').should(
'have.css',
'text-decoration',
'line-through'
)
// retry while accordion css has the
// "display: none" property
cy.get('[data-testid="accordion"]').should('not.have.css', 'display', 'none')
Disabled property
<input type="text" data-testid="example-input" disabled />
cy.get('[data-testid="example-input"]')
.should('be.disabled')
// let's enable this element from the test
.invoke('prop', 'disabled', false)
cy.get('[data-testid="example-input"]')
// we can use "enabled" assertion
.should('be.enabled')
// or negate the "disabled" assertion
.and('not.be.disabled')
Negative assertions
There are positive and negative assertions. Examples of positive assertions are:
cy.get('[data-testid="todo-item"]')
.should('have.length', 2)
.and('have.class', 'completed')
The negative assertions have the "not" chainer prefixed to the assertion. Examples of negative assertions are:
cy.contains('first todo').should('not.have.class', 'completed')
cy.get('[data-testid="loading"]').should('not.be.visible')
False passing tests
Negative assertions may pass for reasons you weren't expecting. Let's say we want to test that a Todo list app adds a new Todo item after typing the Todo and pressing enter.
Positive assertions
When adding an element to the list and using a positive assertion, the test asserts a specific number of Todo items in our application.
The test below may still falsely pass if the application behaves unexpectedly, like adding a blank Todo, instead of adding the new Todo with the text "Write tests".
cy.get('[data-testid="todos"]').should('have.length', 2)
cy.get('[data-testid="new-todo"]').type('Write tests{enter}')
// using a positive assertion to check the
// exact number of items
cy.get('[data-testid="todos"]').should('have.length', 3)
Negative assertions
But when using a negative assertion in the test below, the test can falsely pass when the application behaves in multiple unexpected ways:
- The app deletes the entire list of Todo items instead of inserting the 3rd Todo
- The app deletes a Todo instead of adding a new Todo
- The app adds a blank Todo
cy.get('[data-testid="todos"]').should('have.length', 2)
cy.get('[data-testid="new-todo"]').type('Write tests{enter}')
// using negative assertion to check it's
// not a number of items
cy.get('[data-testid="todos"]').should('not.have.length', 2)
Should callback
If built-in assertions are not enough, you can write your own assertion function
and pass it as a callback to the .should()
command. Cypress will automatically
retry the callback function until it
passes or the command times out. See the
.should()
documentation.
<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 will be a string like "main-abc123 heading-xyz987"
expect(className).to.match(/heading-/)
})
Multiple assertions
You can attach multiple assertions to the same command.
<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')
Note that all chained assertions will use the same reference to the original subject. For example, if you wanted to test a loading element that first appears and then disappears, the following WILL NOT WORK because the same element cannot be visible and invisible at the same time:
// ⛔️ DOES NOT WORK
cy.get('[data-testid="loading"]').should('be.visible').and('not.be.visible')
Instead you should split the assertions and re-query the element:
// ✅ THE CORRECT WAY
cy.get('[data-testid="loading"]').should('be.visible')
cy.get('[data-testid="loading"]').should('not.be.visible')