In this article we’ll cover strategies for testing the various synchronous and asynchronous operations in a browser using the Cypress testing framework.
Cypress and asynchronously
Cypress tests run on the same event loop that is used by the underlying applications. This means all Cypress tests operate asynchronously. Cypress has logic that ensures that commands execute in sequence when those operations are occurring asynchronously.
Consider the following simple Cypress test:
In the code above, we added a line that prints the value “Cypress is Asynchronous” to the browser console. It appears
that this console log statement would execute after we click the
h3 element and an input box.
However, when opening the browser console and running the code above, you’ll observe that the value “Cypress is
Asynchronous” is printed to the browser console before the click on the
h3 element and input box.
Why is this happening? The reason is due to the asynchronous nature of Cypress. The
.get() call simply enqueues the
command onto the main event loop, and the actual execution of that command occurs asynchronously when the enqueued event
is handled. Once the command is enqueued, operation proceeds to the new line of code, which is why the
statement is printed before the actions ever occur.
If we want the above command to be carried out in the correct order, then the
console.log() should be moved inside a
Now when we run the test, we don’t see anything printed in the browser console until after the
h3 and input box
interactions have executed.
Cypress and Promises
Let’s take a look at another example:
cy command returns a Promise, and we define the
.then() callback in each promise to indicate what code should
be executed if the Cypress command completes successfully.
One nice feature of Cypress is that the Cypress runtime has built-in logic to retry failed operations. If, for example, an element is not immediately present on a page, it will retry the action for a certain amount of time before considering the action failed. This built-in behavior can help make tests more resilient to false failures.
The above code can be succinctly written without including promises manually, as shown below:
Strategies for handling async code in Cypress
In this section, we will look at various ways of handling asynchronous code while testing with Cypress. As we covered in our Regression Testing Guide, having a proper testing strategy for dealing with asynchronous behavior is crucial to preventing flaky tests. Below we’ll cover some best practices for dealing with this in your Cypress tests.
Avoid using cy.wait to wait for number of milliseconds
cy.wait() is used to wait for a set number of milliseconds for an element to be resolved before moving on to the next
command. Many developers use
cy.wait() to code hard-coded waits in their tests, perhaps to wait for a network call or
some other operation to complete. Using
cy.wait() for this purpose should be considered a bad practice; at best your
tests will be waiting around doing nothing, and at worst your test could be not waiting long enough for it’s dependent
async action to complete.
Using cy.intercept to handle network requests
There may be circumstances where you want a network request to complete before taking the next action in a test. A more
robust alternative to hard-coded waits is
cy.intercept(). This method can be used intercept and wait for network
requests to complete. This is a very powerful strategy, since it tells the test to wait just long enough for the
asynchronous operation to complete, which keeps tests fast and helps them be more reliable.
Below is an example of how to use
You can visit the official Cypress documentation to find out more
Allow Cypress to handle animations
As previously mentioned, animations will execute asynchronously, and there can be cases where you’ll want to wait for an animation to complete before proceeding to the next action in the test. Fortunately, Cypress automatically detects if an element is animating and then waits until that element stops animating. Cypress does this by taking a sample of an element’s position over time and then determining if the element has moved recently.
In this article, we have taken a look at synchronous and asynchronous programming, components in the browser that load sync and async, Cypress, and the relationship between Cypress and asynchronously. We looked at how Cypress handles promises internally, enabling us to write much cleaner tests that are easily understood and readable.
We also looked at strategies that can be used to handle asynchronous code in Cypress, including avoiding the use of
cy.wait() for halting tests, allowing Cypress to handle animation, and using
cy.intercept() for handling network
Try Reflect: A testing platform with built-in handling of async actions
Reflect is a great alternative to Cypress that lets you build and run tests for your web application without writing code. Reflect automatically detects when asynchronous actions occur (including animations, page loads, and slow network requests), and waits for them to complete before proceeding on to the next test step.
Tired of flaky tests? Try Reflect for free.