Handling Visibility Issues in Cypress Tests: A Case Study

Handling Visibility Issues in Cypress Tests: A Case Study

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

  1. :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.
  2. 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.

Scroll to Top