javascriptnode.jstestingautomated-testscypress

Cypress showing false positive when button is covered by a transparent element


I recently had an issue where a button was covered by a transparent element (z-index and position absolute issue) making the button visible but not clickable.

Cypress passed these tests and was able to both see and click the button.

Is there a way to get Cypress to check if there is a transparent element sitting over another element?

My code is simply:

cy.get('.btn-main').click();

I added

cy.get('.btn-main').should('be.visible');

but it still passes.

Would anyone know how I can get Cypress to make sure the button is actually clickable?


Solution

  • There is a utility method Cypress.dom.getElementAtPointFromViewport() that can be used to tell if something is covering the element you want to interact with.

    It needs the position on screen of the subject, for which you can use another utility Cypress.dom.getElementCoordinatesByPosition().

    Putting them together in a custom command:

    Cypress.Commands.add('isElementCovered', {prevSubject:true}, (subject) => {
      const element = subject[0];
    
      const {getElementCoordinatesByPosition, getElementAtPointFromViewport} = Cypress.dom;
      const position = getElementCoordinatesByPosition(element);
      const {x,y} = position.fromElViewport;
      const doc = cy.state('document');
      const elAtCoords = getElementAtPointFromViewport(doc, x, y);
    
      return elAtCoords === element ? 'element is NOT covered' : 'element IS covered';
    })
    

    This command will find the original element (the button) if it is not covered.
    If it is covered, it will find the other element that covers the subject element (provided z-index is higher).


    Example test

    If I take the example of a fixed overlay from W3-Schools - How TO - Overlay and I add in a button similar to the one you mentioned

    <!DOCTYPE html>
    <html>
    <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    #overlay {
      position: fixed;
      display: none;
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-color: rgba(0,0,0,0.2);
      z-index: 2;
      cursor: pointer;
    }
    </style>
    </head>
    <body>
    
    <div id="overlay" onclick="off()"></div>
    
    <div style="padding:20px">
      <h2>Overlay</h2>
      <p>Add an overlay effect to the page content (100% width and height with a black background color with 50% opacity).</p>
      <button onclick="on()">Turn on overlay effect</button>
        
      <!-- added button to test here -->
      <button onclick="off()" class="btn-main">Main button to test</button> 
         
    </div>
    
    <script>
    function on() {
      document.getElementById("overlay").style.display = "block";
    }
    
    function off() {
      document.getElementById("overlay").style.display = "none";
    }
    </script>
    
    </body>
    </html>
    

    The test will be

    cy.get('div#overlay').should('not.be.visible')   // initially overlay is not visible
    
    cy.get('.btn-main').isElementCovered()
      .should('eq', 'element is NOT covered')        // and the button is not covered
    
    cy.contains('button', 'Turn on overlay effect')
      .click()                                      // turn on the overlay
    cy.get('div#overlay').should('be.visible')      // the overlay now is visible
    
    cy.get('.btn-main').isElementCovered()
      .should('eq', 'element IS covered')       // and the button is covered
    
    cy.get('.btn-main').should('be.visible');   // button still considered visible
    

    enter image description here