Search code examples
javascriptmocha.jscypress

Skipping a test in Cypress conditionally


I'm trying to find out if I'm able to conditionally skip a test it() in my test suite and deal with its async nature as well.

I've read about conditional testing in Cypress docs https://docs.cypress.io/guides/core-concepts/conditional-testing.html and also mochajs documentation about it https://mochajs.org/.

My intention is to check if an error is displayed on a website, and skip the test if it does. Otherwise continue with the assertions.

The code snippet from mochajs that I'm trying to take to my test in Cypress is:

it('should only test in the correct environment', function() {
  if (/* check test environment */) {
    // make assertions
  } else {
    this.skip();
  }
});

So what I've got in Cypress is:

it('shows the list', function() {
    if (queryFailed()) {
      this.skip();
    } else {
      cy.get('.list')
        .should('be.visible')
    }

Note that I changed the arrow function in my it() to be a function() so that I can make use of this.

queryFailed() is a function that checks if the query succeeded or not.

function queryFailed() {
  cy.get('.spin')
    .then($container => {
      const htmlLoaded = $container[0].innerHTML;

      if (htmlLoaded.indexOf('List') !== -1) {
        return false;
      }

      if (htmlLoaded.indexOf('error') !== -1) {
        return true;
      }

      cy.wait(1000);
      queryFailed();
    });
}

Briefly, if the content of the div element I'm waiting for has "error" then I know the query failed so I return true, otherwise I return false.

What I see in my tests after debugging, is that even though the condition works well, the async nature of JS executes the code in the else statement at the same time than the if. So the final output is as if there is no condition at all, since everything is tested.

Is there a better way of dealing with this async feature of JS?


Solution

  • I think you are almost there, but instead of the synchronous if () {...} else {...} pattern you need the asynchronous callback pattern.

    it('shows the list', function() {
    
      const whenFailed = function() {
        this.skip()
      }
    
      const whenSucceeded = function() {
        cy.get('.list').should('be.visible')
      }
    
      queryFailed(whenFailed, whenSucceeded);
    }
    
    function queryFailed(whenFailed, whenSucceeded) {
      cy.get('.spin')
        .then($container => {
          const htmlLoaded = $container[0].innerHTML;
    
          if (htmlLoaded.indexOf('List') !== -1) {
            whenSucceeded();
            return;
          }
    
          if (htmlLoaded.indexOf('error') !== -1) {
            whenFailed();
            return;
          }
    
          cy.wait(1000);
          queryFailed(whenFailed, whenSucceeded);
        });
    }
    

    However, I note the recursive call to queryFailed(), which looks like you are manually retrying the content of the spin container.

    Cypress has built in retries, so all you have to do is decide on a maximum time your result will possibly take (say 20 seconds), and it will conclude the command as soon as the desired content arrives, or fail the test altogether if it doesn't happen in 20 seconds.

    Also, you should be in control of the success/failure of whatever the spinner is waiting on (e.g fetching the list via XHR). So you should split the test into two - one for success and one for failure.

    context('when the list loading succeeds' function() {
    
      it('shows the list', function() {
        // Mock XHR success here
        cy.contains('.spin', 'List', { timeout: 20000 });
        cy.get('.list').should('be.visible');
      })
    
      it('does not show an error message', function() {
        ...
      })
    
    })
    
    context('when the list loading fails' function() {
    
      it('does not show the list', function() {
        // Mock XHR failure here
        cy.contains('.spin', 'error', { timeout: 20000 });
        cy.get('.list').should('not.be.visible');
      })
    
      it('shows an error message', function() {
        ...
      })
    
    })
    

    This is a bit rough as I don't know the exact HTML expected, or what the spinner is waiting on, but you can see this pattern is much tidier and tests all paths in a controlled manner.