Imagine an application that resets the input field on start up. The resets are random but usually happen within the first 200-300 milliseconds. The application code looks something like this:
1 | <input type="text" id="flaky-input" value="" /> |
The Cypress test simply tries to type into the input field.
1 | it('is flaky without retries', () => { |
The test video shows the flaky behavior - the first characters simply disappear.
🎁 You can find the above example amongst the test examples in the [bahmutov/cypress-recurse][cypress-recurse] repo.
Workaround using cypress-recurse
If you are a real user, and you see some of the characters disappear as you type, you would curse, clear the input field, and type the text again. We can do the same using a plugin I wrote cypress-recurse. This plugin performs any provided Cypress commands until the predicate function returns true or the entire command times out. Let's update our spec to make it flake-free:
1 | import { recurse } from 'cypress-recurse' |
The Cypress Command Log shows how the test types in the characters the first time - but then the part of the input disappears. The predicate function ($input) => $input.val() === text
returns false. The recurse
function then repeats the first act function again, clearing the input and typing the entire string, just like a real user does. On the second attempt, the entire text is preserved, and the predicate function returns true, completing the step.
We can control the recurse
function through an options object argument. For example, we can delay each iteration and log less information.
1 | recurse( |
Preventing the flake
Using the cypress-recurse plugin in this case only works around the application's behavior. The real user would see the same broken application. It is better to prevent the input until the application is ready. Thus I advise to add a disabled
attribute to the input element, and only remove it after the application is ready to process the input without clearing it.
1 | <input type="text" id="flaky-input" value="" disabled /> |
The test that uses cypress-recurse
still works in this case, but much more important - the original test now works without any flake!
1 | it('is waiting for the input element to become enabled', () => { |
The cy.type command simply waits for the input element to be enabled (it is a built-in actionability check) before typing. You can see the input grayed out and the TYPE command waiting for two seconds until it starts typing in the video below.
If the application does not let the test runner interact with it until it is ready, the flake problem never appears, so that is the best solution in my opinion.
Videos
You can watch me solve the flake using the cypress-recurse plugin in the video below
Then watch the video of solving the flake problem for real by disabling the input element until the application is ready to receive the user's actions.
For more videos like this with Cypress tips and solutions, subscribe to my video channel at https://www.youtube.com/glebbahmutov.
More information
- bahmutov/cypress-recurse repo
- you can find many blog posts I have written showing the solutions to different flaky test situations on Cypress blog, the posts titles start with "When Can The Test ..." and are tagged Flake
- Cypress Flaky Tests Exercises