should
Create an assertion. Assertions are automatically retried until they pass or time out.
An alias of .and()
Note: .should()
assumes you are already familiar with core concepts such
as assertions
Syntax
.should(chainers)
.should(chainers, value)
.should(chainers, method, value)
.should(callbackFn)
Usage
Correct Usage
cy.get('.error').should('be.empty') // Assert that '.error' is empty
cy.contains('Login').should('be.visible') // Assert that el is visible
cy.wrap({ foo: 'bar' }).its('foo').should('eq', 'bar') // Assert the 'foo' property equals 'bar'
Incorrect Usage
cy.should('eq', '42') // Should not be chained off 'cy'
Arguments
chainers (String)
Any valid chainer that comes from Chai or Chai-jQuery or Sinon-Chai.
value (String)
Value to assert against chainer.
method (String)
A method to be called on the chainer.
callbackFn (Function)
Pass a function that can have any number of explicit assertions within it. Whatever was passed to the function is what is yielded.
Yields
-
In most cases,
.should()
yields the same subject it was given from the previous command.
cy.get('nav') // yields <nav>
.should('be.visible') // yields <nav>
However, some chainers change the subject. In the example below, the second
.should()
yields the string sans-serif
because the chainer
have.css, 'font-family'
changes the subject.
cy.get('nav') // yields <nav>
.should('be.visible') // yields <nav>
.should('have.css', 'font-family') // yields 'sans-serif'
.and('match', /serif/) // yields 'sans-serif'
Examples
Chainers
Assert the checkbox is disabled
cy.get(':checkbox').should('be.disabled')
The current DOM element is yielded
cy.get('option:first')
.should('be.selected')
.then(($option) => {
// $option is yielded
})
Value
Assert the class is 'form-horizontal'
cy.get('form').should('have.class', 'form-horizontal')
Assert the value is not 'Jane'
cy.get('input').should('not.have.value', 'Jane')
The current subject is yielded
cy.get('button')
.should('have.id', 'new-user')
.then(($button) => {
// $button is yielded
})
Method and Value
Assert the anchor element has href attribute
// have.attr comes from chai-jquery
cy.get('#header a').should('have.attr', 'href')
Assert the href attribute is equal to '/users'
cy.get('#header a').should('have.attr', 'href', '/users')
Note: the have.attr
assertion changes the subject from the original
element to the attribute's value
cy.get('#header a') // yields the element
.should('have.attr', 'href') // yields the "href" attribute
.and('equal', '/users') // checks the "href" value
Focus
Assert an input is focused after button click
cy.get('#btn-focuses-input').click()
cy.get('#input-receives-focus').should('have.focus') // equivalent to should('be.focused')
Function
Passing a function to .should()
enables you to make multiple assertions on the
yielded subject. This also gives you the opportunity to massage what you'd
like to assert on.
Be sure not to include any code that has side effects in your callback function. The callback function will be retried over and over again until no assertions within it throw.
You cannot invoke Cypress commands inside of a .should()
callback function.
Use cypress commands before or after .should()
instead.
Incorrect Usage
cy.get('p').should(($p) => {
cy.log($p)
...
})
Correct Usage
cy.get('p').should(($p) => { ... }).log()
// or
cy.get('p').then(($p) => {
...
cy.log($p)
})
<p>
Verify length, content, and classes from multiple <div>
<p class="text-primary">Hello World</p>
<p class="text-danger">You have an error</p>
<p class="text-default">Try again later</p>
</div>
cy.get('p').should(($p) => {
// should have found 3 elements
expect($p).to.have.length(3)
// make sure the first contains some text content
expect($p.first()).to.contain('Hello World')
// use jquery's map to grab all of their classes
// jquery's map returns a new jquery object
const classes = $p.map((i, el) => {
return Cypress.$(el).attr('class')
})
// call classes.get() to make this a plain array
expect(classes.get()).to.deep.eq([
'text-primary',
'text-danger',
'text-default',
])
})
Warning Any value
returned from a .should()
callback function will be ignored. The original
subject will be yielded to the next command.
cy.get('p')
.should(($p) => {
expect($p).to.have.length(3)
return 'foo'
})
.then(($p) => {
// the argument $p will be the 3 elements, not "foo"
})
heading-
Assert class name contains <div class="docs-header">
<div class="main-abc123 heading-xyz987">Introduction</div>
</div>
cy.get('.docs-header')
.find('div')
// .should(cb) callback function will be retried
.should(($div) => {
expect($div).to.have.length(1)
const className = $div[0].className
expect(className).to.match(/heading-/)
})
// .then(cb) callback is not retried,
// it either passes or fails
.then(($div) => {
expect($div).to.have.text('Introduction')
})
You can even throw your own errors from the callback function.
cy.get('.docs-header')
.find('div')
.should(($div) => {
if ($div.length !== 1) {
// you can throw your own errors
throw new Error('Did not find 1 element')
}
const className = $div[0].className
if (!className.match(/heading-/)) {
throw new Error(`No class "heading-" in ${className}`)
}
})
Assert text content of 3 elements
Example below first asserts that there are 3 elements, and then checks the text content of each one.
<ul class="connectors-list">
<li>Walk the dog</li>
<li>Feed the cat</li>
<li>Write JavaScript</li>
</ul>
cy.get('.connectors-list > li').should(($lis) => {
expect($lis).to.have.length(3)
expect($lis.eq(0)).to.contain('Walk the dog')
expect($lis.eq(1)).to.contain('Feed the cat')
expect($lis.eq(2)).to.contain('Write JavaScript')
})
Read Cypress should callback blog post to see more variations of the above example.
For clarity you can pass a string message as a second argument to any expect
assertion, see Chai#expect.
cy.get('.connectors-list > li').should(($lis) => {
expect($lis, '3 items').to.have.length(3)
expect($lis.eq(0), 'first item').to.contain('Walk the dog')
expect($lis.eq(1), 'second item').to.contain('Feed the cat')
expect($lis.eq(2), 'third item').to.contain('Write JavaScript')
})
These string messages will be shown in the Command Log giving each assertion more context.
Compare text values of two elements
The example below gets the text contained within one element and saves it in a closure variable. Then the test gets the text in another element and asserts that the two text values are the same after normalizing.
<div class="company-details">
<div class="title">Acme Developers</div>
<div class="identifier">ACMEDEVELOPERS</div>
</div>
const normalizeText = (s) => s.replace(/\s/g, '').toLowerCase()
// will keep text from title element
let titleText
cy.get('.company-details')
.find('.title')
.then(($title) => {
// save text from the first element
titleText = normalizeText($title.text())
})
cy.get('.company-details')
.find('.identifier')
.should(($identifier) => {
// we can massage text before comparing
const idText = normalizeText($identifier.text())
// text from the title element should already be set
expect(idText, 'ID').to.equal(titleText)
})
Multiple Assertions
Chaining multiple assertions
Cypress makes it easier to chain assertions together.
In this example we use .and()
which is identical to
.should()
.
// our subject is not changed by our first assertion,
// so we can continue to use DOM based assertions
cy.get('option:first').should('be.selected').and('have.value', 'Metallica')
Wait until the assertions pass
Cypress won't resolve your commands until all of its assertions pass.
// Application Code
$('button').click(function () {
$button = $(this)
setTimeout(() => {
$button.removeClass('inactive').addClass('active')
}, 1000)
})
cy.get('button')
.click()
.should('have.class', 'active')
.and('not.have.class', 'inactive')
Notes
Subjects
How do I know which assertions change the subject and which keep it the same?
The chainers that come from Chai or Chai-jQuery will always document what they return.
Using a callback function will not change what is yielded
Whatever is returned in the function is ignored. Cypress always forces the
command to yield the value from the previous cy command's yield (which in the
example below is <button>
)
cy.get('button')
.should(($button) => {
expect({ foo: 'bar' }).to.deep.eq({ foo: 'bar' })
return { foo: 'bar' } // return is ignored, .should() yields <button>
})
.then(($button) => {
// do anything we want with <button>
})
Differences
What's the difference between .then()
and .should()
/.and()
?
Using .then()
allows you to use the yielded subject in a callback function and
should be used when you need to manipulate some values or do some actions.
When using a callback function with .should()
or .and()
, on the other hand,
there is special logic to rerun the callback function until no assertions throw
within it. You should be careful of side affects in a .should()
or .and()
callback function that you would not want performed multiple times.
Rules
Requirements
-
.should()
requires being chained off a previous command.
Timeouts
-
.should()
will continue to retry its specified assertions until it times out.
cy.get('input', { timeout: 10000 }).should('have.value', '10')
// timeout here will be passed down to the '.should()'
// and it will retry for up to 10 secs
cy.get('input', { timeout: 10000 }).should(($input) => {
// timeout here will be passed down to the '.should()'
// unless an assertion throws earlier,
// ALL of the assertions will retry for up to 10 secs
expect($input).to.not.be('disabled')
expect($input).to.not.have.class('error')
expect($input).to.have.value('US')
})
Command Log
Assert that there should be 8 children in a nav
cy.get('.left-nav>.nav').children().should('have.length', 8)
The commands above will display in the Command Log as:
When clicking on assert
within the command log, the console outputs the
following:
History
Version | Changes |
---|---|
11.0.0 | Throw error if Cypress command used in callback |
0.11.4 | Allows callback function argument |
< 0.3.3 | .should() command added |