Iterate Over Elements
There are lots of ways to iterate over page elements using the built-in Cypress commands or cypress-map queries.
cy.each
using<ul id="fruits">
<li>Kiwi</li>
<li>Grapes</li>
</ul>
cy.get('#fruits li').each(($el) => {
expect($el, 'is jQuery').to.satisfy(Cypress.dom.isJquery)
expect($el, 'is not a DOM element').to.not.satisfy(
Cypress._.isElement,
)
})
Unfortunately, cy.each
does not change the current subject, so we cannot easily extract data from multiple elements.
using $.each
We can change the current subject by using jQuery $.each
to iterate over objects, and by putting the iteration inside cy.then
command.
<ul id="fruits">
<li>Kiwi</li>
<li>Grapes</li>
</ul>
cy.get('#fruits li').then(($el) => {
$el.each((k, el) => {
expect(el, 'is DOM element').to.satisfy(Cypress._.isElement)
})
})
Unfortunately, cy.then
is not a retry-able query command, which might cause unexpected flake if the application is still refreshing the DOM elements.
using cy.invoke and $.each
<ul id="fruits">
<li>Kiwi</li>
<li>Grapes</li>
</ul>
cy.get('#fruits li').invoke('each', (k, el) => {
expect(el, 'is DOM element').to.satisfy(Cypress._.isElement)
})
The cy.invoke is a query command, thus it will retry.
using cy.invoke and $.map
We can change the current subject in the chain of commands but using jQuery $.map
method, instead of $.each
. Unfortunately, the subject is still a jQuery object, only instead of DOM elements, it now holds whatever the $.map(callback)
returns.
<ul id="fruits">
<li>Kiwi</li>
<li>Grapes</li>
</ul>
cy.get('#fruits li')
.invoke('map', (k, el) => {
return el.innerText
})
// convert jQuery object to an array
.invoke('toArray')
.should('deep.equal', ['Kiwi', 'Grapes'])
using cy.map
Let's bring the cypress-map queries.
<ul id="fruits">
<li>Kiwi</li>
<li>Grapes</li>
</ul>
cy.get('#fruits li')
// current subject is jQuery<Element> object
.map((el) => {
expect(el, 'is DOM element').to.satisfy(Cypress._.isElement)
return el
})
// the mapped result is an array of elements
.should('be.an', 'Array')
If we need to get something specific from the elements, we need to use DOM element methods.
<ul id="fruits">
<li data-product-id="kiwi">Kiwi</li>
<li data-product-id="grape">Grapes</li>
</ul>
cy.get('#fruits li')
.map((el) => {
return el.dataset.productId
})
.should('deep.equal', ['kiwi', 'grape'])
Tip: you can achieve the same result by using the built-in deep property shortcut in cy.map
cy.get('#fruits li')
.map('dataset.productId')
.should('deep.equal', ['kiwi', 'grape'])
using cy.mapInvoke
<ul id="fruits">
<li data-product-id="kiwi">Kiwi</li>
<li data-product-id="grape">Grapes</li>
</ul>
cy.get('#fruits li')
.map((el) => {
return el.getAttribute('data-product-id')
})
.should('deep.equal', ['kiwi', 'grape'])
The same "invoke" method on each DOM element can be done using cy.mapInvoke
<ul id="fruits">
<li data-product-id="kiwi">Kiwi</li>
<li data-product-id="grape">Grapes</li>
</ul>
cy.get('#fruits li')
.mapInvoke('getAttribute', 'data-product-id')
.should('deep.equal', ['kiwi', 'grape'])
We can even wrap each DOM element using jQuery to be able to its methods, if you really want to.
<ul id="fruits">
<li data-product-id="kiwi">Kiwi</li>
<li data-product-id="grape">Grapes</li>
</ul>
cy.get('#fruits li')
.map((el) => {
return Cypress.$(el).attr('data-product-id')
})
.should('deep.equal', ['kiwi', 'grape'])
Finally, let's take the elements, make a jQuery from each one -> we get an array of jQuery objects, each with one element. Then we can use cy.mapInvoke
to call the jQuery method attr
on each jQuery object.
<ul id="fruits">
<li data-product-id="kiwi">Kiwi</li>
<li data-product-id="grape">Grapes</li>
</ul>
cy.get('#fruits li')
.map(Cypress.$)
.should('be.an', 'Array')
.mapInvoke('attr', 'data-product-id')
.should('deep.equal', ['kiwi', 'grape'])