33

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?

DavidZ
  • 519
  • 1
  • 5
  • 9

9 Answers9

26

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.

Richard Matsen
  • 20,671
  • 3
  • 43
  • 77
  • 3
    Thank you very much for your thorough answer, it works for me! I only had to change the `whenFailed` function definition to be an arrow function, so that `this` would have the context of the `it`. – DavidZ Feb 06 '19 at 13:44
  • How can we achieve the same in Cypress Cucumber? – Ashok kumar Ganesan Oct 20 '21 at 15:04
19

Thank you for the detailed description! I provide you a solution for your very first question

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.

Use an environment variable, I report you a solution of mine (actually using in my pipeline).

if (!Cypress.env("SKIP_E2E_TESTS")) {
  it(...);
}

and in my package.json file I have a script that looks like this

"test": "CYPRESS_SKIP_E2E_TESTS=true npm-run-all --parallel --silent test:unit test:cypress",

My goal was the same as yours, I'd like to disable some tests in some circumstances (the CI pipeline in my case).

So put the whole test into a condition instead of having a conditional test.

Let me know if you need some more help

NoriSte
  • 3,589
  • 1
  • 19
  • 18
  • 2
    Thank you for the answer! I understand that in your case, you want to pass on if you desire to skip the test beforehand, right? In my case, I intend to do it depending on a condition that happens at runtime (I know it's dangerous but it's a tricky case :) – DavidZ Feb 06 '19 at 13:47
17

Note the library @cypress/skip-test has archived. Please check other solutions.

This repository has been archived by the owner on Jan 31, 2023. It is now read-only.


Cypress now also provides a library @cypress/skip-test which can give more controls by

  • cy.skipOn
  • cy.onlyOn
  • isOn
  • boolean flag
  • headed / headless environments
  • ENVIRONMENT
Hongbo Miao
  • 45,290
  • 60
  • 174
  • 267
8

I am using an adapted version of @NoriSte's answer that still registers the tests, but as skipped:

const maybeDescribe = Cypress.env('SOME_ENV_VAR') ? describe : describe.skip

maybeDescribe('Test suite', function() { /* ... */ })

and then similarly, on CI, where I want to skip the tests:

export CYPRESS_SOME_ENV_VAR=
$(npm bin)/cypress run

My source-controlled cypress.json has SOME_ENV_VAR defined so when I run locally, the tests are not skipped.

Ben Mosher
  • 13,251
  • 7
  • 69
  • 80
1

We are using an adapted version by Ben Mosher

const maybeDescribe = Cypress.config("baseUrl") === "https://your-env-url/" 
? describe 
: describe.skip
   
maybeDescribe('Test something... or not dunno', function() { /* */ })

Seems to work for the Github workflow that run tests as well.

user3303019
  • 142
  • 1
  • 11
1

There is a simple way cypress allows this directly in your code:

  • If you want to temporarily run just one part of a test, you can use

    .only(...) // will only execute this part
    
  • if you want to skip a test, use

    .skip(...) // will skip this part
    

You can use them after describe or after it. Insert them like:

describe.skip("Test 1", () => { 
    // ... 
})

describe.only("Test 1", () => { 
    // ... 
})

or:

it.only("Navigate to a web page", () => { ... })   
it.skip("Change data", () => { ... })   

After you're done, just remove the .skip and .only and the complete tests will run again as designed.

A different way is to use JavaScript features to control the flow of your test (if conditions, putting functions around your test parts & comment out the function calls you want to skip etc).

The sky is the limit!

Matt
  • 25,467
  • 18
  • 120
  • 187
  • you can also write xit(...) instead of it.skip(...) And if you want cypress to execute only one test file, you can do this like so (for component tests): cypress run --spec "path/to/your/test.component.cy.ts" --component – Kristian Heitkamp Jul 10 '23 at 11:05
0

The up-to-date way is via @cypress/skip-test. However, the usage for me with cypress v10 and TypeScript was slightly different than in the readme:

yarn add --dev @cypress/skip-test

No further setup needed, simply in the spec:

import { skipOn } from '@cypress/skip-test'

describe('something', () => {
  skipOn(Cypress.env('mySetting') === 'settingValue', () => {
    it('can do something', () => {
      // ...
    })
  })
})

thisismydesign
  • 21,553
  • 9
  • 123
  • 126
  • 1
    The repo "has been archived by the owner on Jan 31, 2023." It should still work but, not sure if there's a recommended alternative. – Zaq Mar 23 '23 at 03:04
0

You can still access the Mocha skip function because Cypress uses Mocha internally. Access Mocha context using cy.state('runnable').ctx, and call skip() on that.

In TypeScript:

// @ts-ignore "cy.state" is not in the "cy" type
export const getMochaContext = () => cy.state('runnable').ctx as Mocha.Context;

Example usage in a test, you'll see the test as skipped and the obvious assertion error after the skip() is not reported.

it("should be skipped", () => {
  getMochaContext().skip();
  expect(0).to.equal(1);
});
w5l
  • 5,341
  • 1
  • 25
  • 43
-2

The Cypress Real World App, a payment application to demonstrate real-world usage of Cypress testing methods, patterns, and workflows contains a few workflows for conditionally running tests in CI and altered workflows for mobile viewports, for instance.

In addition, as of Cypress v4.8.0 certain Test Configurations may be applied to a suite or individual test.

For instance:

it('Show warning outside Chrome', {  browser: '!chrome' }, () => {
  cy.get('.browser-warning')
   .should('contain', 'For optimal viewing, use Chrome browser')
})
Kevin Old
  • 969
  • 1
  • 9
  • 20