01: Use Should With A Callback
These examples show the test behavior from the lesson "Use .should() with a callback" in the course 99 Cypress.io Tips. 📺 Watch this recipe explained in the video Lesson 1 From "99 Cypress.io Tips" Course.
Separate queries
<ul>
<li data-cy="card-text">Milk</li>
<li data-cy="card-text">Bread</li>
<li data-cy="card-text">Juice</li>
</ul>
We can check each element's separately
cy.get('[data-cy=card-text]').eq(0).should('have.text', 'Milk')
cy.get('[data-cy=card-text]').eq(1).should('have.text', 'Bread')
cy.get('[data-cy=card-text]').eq(2).should('have.text', 'Juice')
Combined cy.then callback
<ul>
<li data-cy="card-text">Milk</li>
<li data-cy="card-text">Bread</li>
<li data-cy="card-text">Juice</li>
</ul>
We can check every element inside the jQuery object inside the cy.then
callback function using the Chai-jQuery assertions like expect ... to have text
.
cy.get('[data-cy=card-text]').then(($cards) => {
expect($cards[0]).to.have.text('Milk')
expect($cards[1]).to.have.text('Bread')
expect($cards[2]).to.have.text('Juice')
})
cy.then does not retry
What happens if the cards are loaded after a delay? If there are NO cards, then the cy.get('[data-cy=card-text]')
retries until at least one element is found. In our case, all 3 cards arrive at once.
<ul id="list"></ul>
setTimeout(() => {
document.getElementById('list').innerHTML = `
<li data-cy="card-text">Milk</li>
<li data-cy="card-text">Bread</li>
<li data-cy="card-text">Juice</li>
`
}, 1000)
// the cy.get retries until at least one element is found
cy.get('[data-cy=card-text]')
// the jQuery object is passed to "cy.then" callback
.then(($cards) => {
expect($cards[0]).to.have.text('Milk')
expect($cards[1]).to.have.text('Bread')
expect($cards[2]).to.have.text('Juice')
})
But what happens if the cards load one by one? What happens if the cards load their text dynamically? Let's find out. Remove the "skip" modifier from the next test.
<ul id="list"></ul>
// the cards are added one by one
// - after 1 second the first card appears
// - after another second the second card appears
// - after another second the third card appears
setTimeout(() => {
document.getElementById('list').innerHTML = `
<li data-cy="card-text">Milk</li>
`
}, 1000)
setTimeout(() => {
document.getElementById('list').innerHTML += `
<li data-cy="card-text">Bread</li>
`
}, 2000)
setTimeout(() => {
document.getElementById('list').innerHTML += `
<li data-cy="card-text">Juice</li>
`
}, 3000)
cy.get('[data-cy=card-text]').then(($cards) => {
expect($cards[0]).to.have.text('Milk')
expect($cards[1]).to.have.text('Bread')
expect($cards[2]).to.have.text('Juice')
})
The test fails when it tries to check the $cards[1]
which is undefined; only the first card element is present in the jQuery object $cards
.
cy.should callback retries
Let's replace cy.then(callback)
with cy.should(callback)
. If the callback throws an error, Cypress retries the entire query chain before the cy.should('callback)
and tries it again with the new jQuery object.
<ul id="list"></ul>
// the cards are added one by one
// - after 1 second the first card appears
// - after another second the second card appears
// - after another second the third card appears
setTimeout(() => {
document.getElementById('list').innerHTML = `
<li data-cy="card-text">Milk</li>
`
}, 1000)
setTimeout(() => {
document.getElementById('list').innerHTML += `
<li data-cy="card-text">Bread</li>
`
}, 2000)
setTimeout(() => {
document.getElementById('list').innerHTML += `
<li data-cy="card-text">Juice</li>
`
}, 3000)
cy.get('[data-cy=card-text]').should(($cards) => {
expect($cards[0]).to.have.text('Milk')
expect($cards[1]).to.have.text('Bread')
expect($cards[2]).to.have.text('Juice')
})
The test finishes in 3 seconds, when the 3rd element is appended to the list and every assertion in the cy.should(callback)
finishes.
Use better assertions
Instead of verbose cy.should(callback)
, you can find better queries and assertions, like the ones provided by the plugin cypress-map. For example, you can use the Custom Cypress Should Read Assertion to have an elegant and short test:
<ul id="list"></ul>
// the cards are added one by one
// - after 1 second the first card appears
// - after another second the second card appears
// - after another second the third card appears
setTimeout(() => {
document.getElementById('list').innerHTML = `
<li data-cy="card-text">Milk</li>
`
}, 1000)
setTimeout(() => {
document.getElementById('list').innerHTML += `
<li data-cy="card-text">Bread</li>
`
}, 2000)
setTimeout(() => {
document.getElementById('list').innerHTML += `
<li data-cy="card-text">Juice</li>
`
}, 3000)
// the assertion "should read" comes from cypress-map plugin
cy.get('[data-cy=card-text]').should('read', [
'Milk',
'Bread',
'Juice',
])