It can be difficult to determine what is executing synchronously vs asynchronously within a web application. Static assets in the page, such as images, CSS, and other media, are requested in order but can be processed either synchronously or asynchronously depending on the type of asset it is, and how it’s defined within the page. Cascading style sheets (CSS) are evaluated synchronously by the browser, but images, fonts, and videos referenced inside the CSS are loaded and display asynchronously. Meanwhile, JavaScript exposes a single-threaded, asynchronous programming model to the developer, but is executed in the order it appears on the page unless otherwise specified by the developer.
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 console.log()
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
then()
callback:
|
|
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
Promises are used to handle asynchronous operations in JavaScript. They are simple to manage, even when dealing with multiple asynchronous operations.
Let’s take a look at another example:
|
|
Each 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:
|
|
One major limitation of Cypress’s async behavior is that you can’t use the async / await constructs that are native to JavaScript. This is unfortunate because async / await helps async code be more readable and look more like synchronous code, and for these reasons is often preferred to Promise callbacks by JavaScript developers.
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 cy.intercept()
:
|
|
You can visit the official Cypress documentation to find out more
about the cy.intercept()
command.
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.
Conclusion
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
requests.
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.