When writing end-to-end tests, one common scenario involves verifying that certain elements on a page possess specific attributes or values. Cypress provides various methods to handle such situations efficiently. We will explore how to verify that the title
attribute of multiple elements contains one of several expected values. I’ll use an example to demonstrate the problem and present a robust solution.
Problem Description
Consider the following HTML structure:
<div class="wrapper">
<a title="value 1"></a>
<a title="value 2"></a>
<a title="value 3"></a>
</div>
We have three <a>
elements within a .wrapper
div, each having a title
attribute with a distinct value. Our goal is to write a Cypress test that ensures the title
attribute of all three elements is one of the expected values: “value 1”, “value 2”, or “value 3”. The challenge is to perform this check to accommodate potential changes in the order of these elements.
Initial Approach
Here’s an initial attempt to solve this problem:
cy.get('.wrapper')
.children().as('socialLinks');
cy.get('@socialLinks')
.should('have.attr', 'title').as('title');
expect('@title').to.contain.oneOf('value 1', 'value 2', 'value 3');
In this approach, we first select the children of the .wrapper
and alias them as @socialLinks
. Then, we attempt to assert that the title
attribute of @socialLinks
contains one of the expected values. However, this method does not work as intended because expect
cannot be used directly with Cypress aliases in this manner.
Solution Using .each()
Instead of using a for-loop, which can be problematic in Cypress tests, we can leverage the .each()
command. This command iterates over each element in a set, allowing us to perform assertions on each element individually. Here’s how we can rewrite the test using .each()
:
cy.get('.wrapper').children()
.each(($el) => {
const title = $el.attr('title');
expect(title).to.be.oneOf(['value 1', 'value 2', 'value 3']);
});
In this solution, cy.get('.wrapper').children()
selects all child elements of the .wrapper
. The .each()
command iterates over each of these elements. Inside the callback function, we retrieve the title
attribute of the current element and use Chai’s expect
to assert that it is one of the expected values.
Handling Asynchronous Data Loading
If the values of the title
attributes are loaded from an API or if there is any delay in rendering the elements, the previous solution might fail because the assertions are performed immediately. To handle such scenarios, we can replace the expect
with a should
to ensure that Cypress retries the assertion until it passes or the timeout is reached. Here’s the revised solution:
cy.get('.wrapper').children()
.each(($el) => {
cy.wrap($el).invoke('attr', 'title')
.should('be.oneOf', ['value 1', 'value 2', 'value 3']);
});
In this version, we use cy.wrap($el)
to wrap the current element in a Cypress chainable object. Then, we invoke the attr
method to retrieve the title
attribute and use the should
assertion to check that it is one of the expected values. This approach ensures that the test will retry the assertion until the condition is met or the command times out.
Summary
We’ve discussed how to verify that the title
attribute of multiple elements contains one of several expected values using Cypress. We started with an initial approach that did not work and moved on to a more robust solution using the .each()
command. We also covered how to handle asynchronous data loading by replacing expect
with should
.
Here are the key takeaways:
- Use Cypress’s
.each()
command to iterate over elements and perform assertions on each one. - Use
cy.wrap()
to convert a jQuery element into a Cypress chainable object. - Replace
expect
withshould
for assertions that might involve asynchronous operations.
Following these guidelines allows you to write more reliable and maintainable Cypress tests for verifying element attributes.