When writing automated tests with Cypress, you might encounter issues where elements are not interactable due to their visibility on the page. This can be particularly challenging when elements are conditionally rendered based on different layouts or screen sizes. Here I will explore a real-world example of testing a logout functionality and how to handle such visibility issue effectively.
The Scenario
You are testing the logout functionality of a web application. The logout process involves clicking a button to open an account menu and then clicking another button to log out. Here’s the initial code:
Cypress.Commands.add('logOut', () => {
cy.get('button[data-cy="account-menu-button"]').click();
cy.get('button[data-cy="account-menu-logout-button"]').click();
});
The Problem
When running the test, Cypress throws the following error:
cy.click() can only be called on a single element. Your subject contained 2 elements. Pass { multiple: true } if you want to serially click each element.
Initial Debugging
To ensure only one element is selected, you modify the code by adding the .eq(0) command:
Cypress.Commands.add('logOut', () => {
cy.get('button[data-cy="account-menu-button"]').eq(0).click();
cy.get('button[data-cy="account-menu-logout-button"]').click();
});
However, this leads to another error:
Timed out retrying after 4050ms: cy.click() failed because this element is not visible:
<button class="flex h-7 w-7 items-center justify-center rounded-full bg-flow-turquoise text-sm font-medium text-flow-purple md:bg-flow-purple md:bg-opacity-20" data-cy="account-menu-button" type="button">AU</button>
Root Cause Analysis
The error indicates that the element is not visible because its parent element has display: none
. This suggests that multiple versions of the button exist on the page for different layouts, but only one is visible at any given time.
The Solution
Instead of forcing the click, which can lead to unreliable tests, we use the :visible
pseudo-class to ensure we interact with the visible button:
Cypress.Commands.add('logOut', () => {
cy.get('button[data-cy="account-menu-button"]:visible').click();
cy.get('button[data-cy="account-menu-logout-button"]').click();
});
Explanation
:visible
Selector: This ensures that Cypress interacts only with elements that are currently visible. This is a better practice than using{ force: true }
because it mimics actual user behavior.- Reliability: Forcing clicks can mask underlying issues in the application, such as layout bugs that hide elements. Using visibility checks ensures that your tests fail when the UI is not in a state that a user can interact with.
Final Thoughts
Handling visibility in Cypress tests can be tricky, especially in responsive or conditionally rendered UIs. Using selectors like :visible
helps create more reliable and accurate tests. Here’s the final code snippet for the logout functionality:
Cypress.Commands.add('logOut', () => {
cy.get('button[data-cy="account-menu-button"]:visible').click();
cy.get('button[data-cy="account-menu-logout-button"]').click();
});
By ensuring that Cypress only interacts with visible elements, you maintain the integrity of your tests and ensure they reflect real user interactions. This approach helps catch bugs that might otherwise be missed if you forced interactions on hidden elements.
Implementing these best practices will make your Cypress tests more robust and reliable, leading to better test coverage and fewer false positives.