How can I retry a failed test? - continuous-integration

I'll sometimes have 1 or 2 tests that fail in CI, and rerunning the build causes them to pass.
How can I automatically re-run these flaky tests so my build will pass the first time? Is there something similar to mocha's this.retries?
For example, I have a test that fails with "The element has an effective height of 0x0" about 10% of the time:
cy.visit('/')
cy.get('.my-element').click() // sometimes fails with not visible error

Update (v5.0.0)
Cypress now has built-in retry support.
You can set test retries in Cypress 5.0 via configuration in cypress.json
{
"retries": 1
}
or specify different options for runMode and openMode:
{
"retries": {
"runMode": 1,
"openMode": 3
}
}
runMode allows you to define the number of test retries when running cypress run
openMode allows you to define the number of test retries when running cypress open
You can turn on test retries for just a single test or suite via test options:
it('my test', {
retries: 2
}, () => {
// ...
})
// or
describe('my suite', {
retries: 2
}, () => {
// ...
})
If a test fails in a beforeEach, afterEach, or in the test body, it will be retried. Failures in beforeAll and afterAll hooks will not retry.
Old answer:
Official Support for test retries is on the way, but there's a plugin for that. cypress-plugin-retries
Disclosure: I'm the creator of the plugin.
Installation
Add the plugin to devDependencies
npm install -D cypress-plugin-retries
At the top of cypress/support/index.js:
require('cypress-plugin-retries')
Usage
Use the environment variable CYPRESS_RETRIES to set the retry number:
CYPRESS_RETRIES=2 npm run cypress
or use Cypress.env('RETRIES') in your spec file:
Cypress.env('RETRIES', 2)
or on a per-test or per-hook basis, set the retry number:
Note: this plugin adds Cypress.currentTest and you should only access it in the context of this plugin.
it('test', () => {
Cypress.currentTest.retries(2)
})
Note: Please refer to this issue for updates about official cypress retry support

Cypress 5 now has a native support for retries. Check: https://cypress.io/blog/2020/08/19/introducing-test-retries-in-cypress-5-0

For retry, you can add it in your config as below
{
"retries": {
// Configure retry attempts for `cypress run`
// Default is 0
"runMode": 2,
// Configure retry attempts for `cypress open`
// Default is 0
"openMode": 0
}
}
but instead of adding above I prefer to add (Which works for both run and open mode)
"retries": 1
Points to consider
You need cypress version > 5
As of now in cypress, you can retry test, not entire spec which will be a great ability to make tests more robust. I have already voted this as a critical feature (Also this one of the most voted for now)
Ref:
https://portal.productboard.com/cypress-io/1-cypress-dashboard/tabs/1-under-consideration

Cypress supports test retries as of version 5.0.0, released on 8/19/2020. These are present to reduce test flakiness and continuous integration build failures. This feature is documented in the online Cypress documentation under the Test Retries guide.
By default, tests will not retry when they fail. To retry failing tests, test retries need to be enabled in the configuration.
Retries can be configured separately for run mode (cypress run) vs. open mode (cypress open), as these will typically be different.
There are two ways to configure retries: globally, and per test or test suite.
Global Configuration
The Cypress configuration file (cypress.json by default) allows configuring the retry attempt either per mode or for all modes.
To use a different value per mode:
{
"retries": {
"runMode": 2,
"openMode": 0
}
}
To use the same value for both modes:
{
"retries": 1
}
Single test configuration
A test's configuration can specify the number of retry attempts specific to that test:
// Customize retry attempts for an individual test
describe('User sign-up and login', () => {
// `it` test block with no custom configuration
it('should redirect unauthenticated user to sign-in page', () => {
// ...
})
// `it` test block with custom configuration
it(
'allows user to login',
{
retries: {
runMode: 2,
openMode: 1,
},
},
() => {
// ...
}
)
})
Single test suite configuration
A test suite's configuration can specify the number of retry attempts for each test within that suite:
// Customizing retry attempts for a suite of tests
describe('User bank accounts', {
retries: {
runMode: 2,
openMode: 1,
}
}, () => {
// The per-suite configuration is applied to each test
// If a test fails, it will be retried
it('allows a user to view their transactions', () => {
// ...
}
it('allows a user to edit their transactions', () => {
// ...
}
})

Related

Cypress - Unable to run multiple tests

I am very new to cypress automation and have been following though some examples and have ran into an issue that does not appear to be addressed in any video I have seen, where multiple tests in the same 'describe' do not run as expected.
If I create the following code & run it, it all works perfectly:-
describe('My First Test', () => {
it('Open Google', () => {
cy.visit('https://google.co.uk')
cy.get('#L2AGLb > .QS5gu').click()
cy.get('.gLFyf').type('Automation Step by Step{Enter}')
})
})
I have then attempted to split up the test into individual tests, as follows:-
describe('My First Test', () => {
it('Open Google', () => {
cy.visit('https://google.co.uk')
})
it('Confirm warning', () => {
cy.get('#L2AGLb > .QS5gu').click()
cy.get('.gLFyf').type('Automation Step by Step{Enter}')
})
it('Confirm warning', () => {
cy.get('.gLFyf').type('Automation Step by Step{Enter}')
})
})
The problem now is that after opening Chrome and then going into the next test, which should allow me to type the text, a 'default blank page' is displayed and the tests then fail.
What am I missing here to be able to run these three tests fully?
Code in VS Code
Error after opening Chrome & attempting to type in box
As above really, I was hoping to be able to run all three simple tests together.
EDIT - I rolled back my version of Cypress to 10.10.0 and it works perfectly, so no idea what has changed on the latest version released yesterday.
Try it with Test Isolation turned to false.
Best Practice: Tests should always be able to be run independently from one another and still pass
This was added in Cypress 12.0.0.
But if you want to play without it,
Test Isolation Disabled
testIsolation
beforeEach test
true
- clears page by visiting about:blank
- clears cookies in all domains
- local storage in all domains
- session storage in all domains
false
does not alter the current browser context
cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
testIsolation: false,
},
})
You should visit your page for every test. You can put the cy.visit in a beforeEach function.

How to stop cypress from closing browser after each test case (it)?

I have a cy.js file like this
/// <reference types="cypress" />
context("Dummy test cases", () => {
it("open Google", () => {
cy.visit("www.google.com");
});
it("search a keyword", () => {
cy.get(".gLFyf").type("cypress{enter}");
});
});
As you can see I've 2 test cases. 1 is to open a website and the other is to do other tasks.
But the problem is that after finishing the first 'it' (test case 1), the browser closes and then the next case fails because there is no active browser.
Can you help me how to stop cypress from closing the browser after the execution of each test case?
I found Cypress is quite opinionated about some things, one at the top of the list is "test isolation" which means one test must not influence another test.
Note each it() is a test.
To make your code work, you must turn off test isolation.
If the cypress.config.js file add the option
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
testIsolation: false,
},
})
Same if you are using typescript.
This got added in the new Version Cypress 12.0.0, in their
Changelogs its at the feature section:
Added a new configuration option called testIsolation, which defaults to true.
As #Joylette already said, in your config file just use testIsolation: false to deactivate it. If you ever use an older version use testIsolation: off, they renamed it in the newest version.

When a before hook fails; the tests within that suite are skipped; how could i reliably get the list of those skipped tests?

When a before hook fails; the tests within that suite are skipped; how could i reliably get the list of those skipped tests ?
I tried to listen to the EVENT_TEST_FAIL on the Mocha runner and extract the information from there:
const Mocha = require('mocha');
const {
EVENT_TEST_FAIL,
} = Mocha.Runner.constants;
class MyReporter {
constructor(runner) {
runner
.on(EVENT_TEST_FAIL, (test, err) => {
console.log(err, test);
console.log("failed tests", test.parent.tests.map(t => t.fullTitle()));
});
}
}
module.exports = MyReporter;
But only issue is that, this code cannot distinguish between a failure in the before hook and a failure in a particular test case.
As a result, this code reports all tests in the suite as failed, even when only one test is failed.

Retries in Cypress and Before hooks

Morning all. I have a slightly unusual design to my tests. A typical example might be
describe('1', () => {
describe('2', () => {
before()
describe('3', () => {
it('1')
// ...
it('n')
});
});
});
If there is a failure in one of the individual tests (it 1..n), I want to re-run ALL of those tests, and run the "before" code first too - ie from "describe 2". If I use a before hook then re-runs don't trigger that again. If I change to a beforeEach, then it gets called before each and every "it" block, which I don't want.
Effectively, each it block is a test check, describe 3 is a test step, describe 2 a test spec, and describe 1 a test "group"
Can anyone suggest a way I can re-run a test spec (describe 2) when one test check fails, including re-running the before code for that spec?
(I know this is probably anti-pattern etc, but....)
You can externalise the before() callback function, and use the test:after:run event to trigger it on a retry.
I haven't tested this extensively, but the gist is
const beforeCallback = () => {...}
before(beforeCallback)
Cypress.on('test:after:run', (result) => {
if (result.currentRetry < result.retries && result.state === 'failed') {
beforeCallback()
}
})
it('fails', {retries:3}, () => expect(false).to.eq(true)) // failing test to check it out

Failed screenshot in Mochawesome Report in case of Test Retry

I am using addContext() that mocha provides to append failure screenshots in the Mochawesome html report. I have written this under support/index.js
Cypress.on('test:after:run', (test, runnable) => {
if (test.state === 'failed') {
const screenshot = `FailureScreenshots/${Cypress.spec.name
}/${runnable.parent.title} -- ${test.title} (failed).png`;
addContext({ test }, screenshot);
}
});
This works perfectly when there is a failure(and there are no Test Retries) it just appends the failure screenshot in the html report bases on test.state. However, in case of Test Retries, where on the first run the test failed but on the second run the test passed, it still attaches the failure screenshot. How can I prevent that? It should only append the screenshot when the test finally fails after the number of retries have been exhausted.

Resources