each
Iterate through an array like structure (arrays or objects with a length
property).
Syntax​
.each(callbackFn)
.each(options, callbackFn)
Usage​
Correct Usage
cy.get('ul>li').each(() => {...}) // Iterate through each 'li'
cy.getCookies().each(() => {...}) // Iterate through each cookie
Incorrect Usage
cy.each(() => {...}) // Errors, cannot be chained off 'cy'
cy.clock().each(() => {...}) // Errors, 'clock' does not yield an array
Arguments​
options (Object)
Pass in an options object to change the default behavior of .each().
| Option | Default | Description |
|---|---|---|
timeout | defaultCommandTimeout | Time to wait for each invocation of the callback function to resolve before timing out |
callbackFn (Function)
Pass a function that is invoked with the following arguments:
value- the current item in the collection. If the item is a DOM element, Cypress wraps it in jQuery before passing it to the callback.index- the 0-based index of the current item in the collectioncollection- the original collection being iterated over
Yields ​
.each()yields the same subject it was given.- Because
.each()is not a query, it is unsafe to chain further commands that act or assert on DOM elements after.each(), as those elements may have re-rendered and gone stale during iteration. Reading the yielded subject in a.then()callback (as in the example below) is safe.
Examples​
DOM Elements​
Iterate over an array of DOM elements
Each element arrives in the callback wrapped in jQuery, so you can read its
state with jQuery methods. Wrap it with cy.wrap() to
run Cypress commands on it.
cy.get('[data-cy="product-card"]').each(($card) => {
if ($card.find('[data-cy="sold-out"]').length) {
cy.wrap($card).find('button').should('be.disabled')
} else {
cy.wrap($card).find('button').should('be.enabled')
}
})
Assert the expected length before iterating
cy.get() resolves as soon as it finds at least one
matching element, and .each() does not retry. If your application renders a
list in stages, .each() iterates only the items that exist at that moment.
Assert on the expected length first so the test waits for every item.
cy.get('[data-cy="result"]')
.should('have.length', 2)
.each(($result) => {
cy.wrap($result).should('contain', 'Cypress')
})
Scope commands to each element with .within()
cy.get('table tbody tr').each(($row) => {
cy.wrap($row).within(() => {
cy.get('td').eq(2).should('not.be.empty')
})
})
Re-query by index when an action changes the page
If an action in the callback navigates away or re-renders the list, the
elements .each() passes in are detached from the DOM. Use index to query a
fresh element on every iteration instead.
cy.get('nav a').each(($link, index) => {
cy.get('nav a').eq(index).click()
cy.go('back')
})
Collect results from Cypress commands, then assert once
When each item needs a Cypress command, such as a request, gather the results
in the callback and assert on all of them in a .then()
that runs after the loop finishes. This test reports every broken link at once
instead of stopping at the first one.
const broken = []
cy.get('footer a')
.each(($link) => {
const href = $link.prop('href')
cy.request({ url: href, failOnStatusCode: false }).then((response) => {
if (response.status >= 400) {
broken.push(`${href} (${response.status})`)
}
})
})
.then(() => {
// list the links in the message, since Chai shortens long arrays
expect(broken, `broken links: ${broken.join(', ')}`).to.be.empty
})
If you only need to read values from the elements, such as their text, use a
.should() callback instead.
The original collection is always yielded
No matter what the callback function returns, .each() always yields the
original collection.
cy.get('li')
.should('have.length', 3)
.each(($li, index, $lis) => {
return 'something else'
})
.then(($lis) => {
expect($lis).to.have.length(3) // true
})
Here the .then() callback only reads the yielded array, which is safe. If
instead you needed to act or assert on DOM elements that your application may
have re-rendered during iteration, re-query them with a Cypress query rather
than reusing the subject yielded by .each().
Arrays​
Iterate over fixture data
.each() works on any array, not only DOM elements. Cypress commands you call
in the callback run in order, one item at a time.
// users.json contains an array of user objects
cy.fixture('users.json').each((user) => {
cy.request('POST', '/api/users', user).its('status').should('eq', 201)
})
Promises​
Promises are awaited
If your callback function returns a Promise, .each() waits for it to resolve
before iterating over the next item in the collection.
cy.wrap([1, 2, 3]).each((num, i, array) => {
return new Cypress.Promise((resolve) => {
setTimeout(() => {
resolve()
}, num * 100)
})
})
Options​
Wait longer for each callback to resolve
The timeout option applies to each invocation of the callback, not to the
loop as a whole.
cy.getCookies().each({ timeout: 10000 }, (cookie) => {
return new Cypress.Promise((resolve) => {
// work with each cookie, then resolve
resolve()
})
})
Notes​
Prefer .should() for assertions on every element​
Use a .should() callback when you only need to assert
.each() runs its callback once and does not
retry. When all you need is an assertion
about every element, a .should() callback
reruns until the assertions pass or time out, so it tolerates elements that are
still rendering or updating.
cy.get('[data-cy="result"]').should(($results) => {
expect($results).to.have.length.greaterThan(0)
$results.each((index, result) => {
expect(result.textContent).to.contain('Cypress')
})
})
Because the whole callback reruns, the same pattern works for checks on the list as a whole, such as sort order after your application applies a sort.
cy.get('[data-cy="price"]').should(($prices) => {
const prices = [...$prices].map((price) =>
Number(price.textContent.replace('$', ''))
)
expect(prices).to.deep.equal([...prices].sort((a, b) => a - b))
})
Chaining .should() after .each() does not give you this retry. .should()
reruns only its own callback, not .each(), so it keeps checking values
collected from the list as it was when .each() ran.
Keep .each() for work that needs Cypress commands per item, such as clicking,
typing, or making requests.
Return early​
Stop each prematurely
You can stop the .each() loop early by returning false in the callback
function. The callback must return false synchronously. A Promise that
resolves to false does not stop the loop.
cy.get('ul>li').each(($li) => {
if ($li.hasClass('last-visible')) {
// stop iterating after this item
return false
}
})
Empty collections​
The callback is not invoked for an empty collection
When the subject has a length of 0, .each() skips the callback entirely
and yields the empty subject. It does not fail or wait for items to appear.
A DOM query such as cy.get() fails when no elements
match, so an empty subject usually comes from non-DOM data, such as
cy.wrap([]), cy.fixture(), or
cy.getCookies(). Assert on the length first when
the data should never be empty.
Callback return values​
Don't mix Cypress commands with a synchronous return value
The callback follows the same rules as a .then()
callback. If it calls Cypress commands and returns a truthy synchronous
value (anything other than a falsy value or a Promise-like object with a
.then() method), Cypress throws an
error because you are mixing up async and sync code. Whatever the callback
returns, .each() still yields the original collection.
Rules​
Requirements ​
.each()requires being chained off a previous command..each()requires its subject to have alengthproperty, such as an array or a jQuery collection. Any other subject fails with an error.
Assertions ​
.each()will only run assertions you have chained once, and will not retry.
Timeouts ​
.each()can time out waiting for a promise you've returned to resolve. The timeout applies to each invocation of the callback and defaults todefaultCommandTimeout.
Command Log​
.each()does not log in the Command Log unless it fails, for example when its callback times out or its subject is not array-like.