Skip Dependent Cypress Tests On Failure

How to better execute E2E tests that depend on each other.

Sometimes you want your tests to be dependent on each other. For example, instead of a single long E2E test, I have created small focused "step" tests. Each test starts where the previous test left off; the test isolation flag is set to false.

📺 You can watch this blog post example explained in the video Skip Dependent Cypress Tests On Failure.

cypress/e2e/buy-item.cy.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { LoginInfo } from '.'
import { LoginPage } from '@support/pages/login.page'

describe(
'User',
{ viewportHeight: 1200, testIsolation: false, scrollBehavior: false },
() => {
const user: LoginInfo = Cypress.env('users').standard

it('logs in', () => {
LoginPage.login(user.username, user.password)
cy.visit('/inventory')
})

it('selects an item', () => {
cy.get('.inventory_item')
.should('have.length.above', 2)
.first()
.contains('button', 'Add to cart')
.click()
cy.get('.inventory_item').first().contains('button', 'Remove')
})

it('goes to the cart', () => {
cy.get('.shopping_cart_container').click()
cy.location('pathname').should('equal', '/cart')
})

it('goes to checkout', () => {
cy.contains('button', 'Checkout').click()
cy.location('pathname').should('equal', '/checkout-step-one')
cy.get('input[placeholder="First Name"]').type('Joe')
cy.get('input[placeholder="Last Name"]').type('Smith')
cy.get('input[placeholder="Zip/Postal Code"]').type('90210')
cy.get('[data-test=continue]').click()
cy.location('pathname').should('equal', '/checkout-step-two')
})

it('completes the purchase', () => {
cy.contains('button', 'Finish').click()
cy.location('pathname').should('equal', '/checkout-complete')
cy.get('.checkout_complete_container').should('be.visible')
})
},
)

🎓 This example comes from one of the lessons in my Testing The Swag Store online course.

If the tests pass, all is good. The tests are quick.

But what happens when we fail to log in? For example, if we accidentally quote the username:

1
2
3
4
it('logs in', () => {
LoginPage.login('user.username', user.password)
cy.visit('/inventory')
})

The first test fails, and each test after that also fails - it takes a while to fail, since each command retries.

All tests fail since we could not even log in

If a test fails, we want to skip all tests after it. I wrote a plugin cypress-skip-this-test to do precisely that. Install that plugin as a dev dependency and call the exported function before each test in the suite of dependent tests.

cypress/e2e/buy-item.cy.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { skipIfPreviousTestsFailed } from 'cypress-skip-this-test'

describe(
'User',
{ viewportHeight: 1200, testIsolation: false, scrollBehavior: false },
() => {
beforeEach(skipIfPreviousTestsFailed)

it('logs in', () => {
LoginPage.login('user.username', user.password)
cy.visit('/inventory')
})

...
}
)

The function skipIfPreviousTestsFailed checks the parent suite of the current test. If a previous test fails, then the current test is skipped. It is a standard Mocha syntax:

cypress-skip-this-test/src/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const getMochaContext = () => cy.state('runnable').ctx

const skipIfPreviousTestsFailed = () => {
const ctx = getMochaContext()
const thisTestIndex = ctx.test.parent?.tests?.indexOf(ctx.test)
const previousTests = ctx.test.parent?.tests?.slice(
0,
thisTestIndex,
)
const anyFailedTests = previousTests?.some(
(test) => test.state === 'failed',
)
if (anyFailedTests) {
return ctx.skip()
}
}

Simple and effective. You can find even simpler example in the bahmutov/cypress-skip-example repo.