In the work of an automation tester, one of the key tasks is the correct handling of waits for the events, which allows us to perform assertions on an already prepared element. In testers jargon, we encounter the term “waits” from the wait() method, and this nomenclature is used in this article.
In this post, I will elaborate on what waits are needed for in automated tests, present a comparison of waits available in Selenium and in Cypress, along with an example implementation in Cypress.
Cypress significantly stand out from other available applications for automated testing due to one specific wait, which will also be described along with the cy.wait() and cy.intercept() methods.
The last aspect raised in this article will be recursion, which can also be used in Cypress to wait for an item, as its own custom approach.
Wait in automated tests
Wait is one of the basic and key parts of an automated test. It allows us to perform a delay in the test to, in order to load the web page, or any element on that page, correctly. This will make it possible to check, that the results in a positive or negative completion of the test.
Wait’s wrong implementation can result in an incorrect test result, e.g. by trying to interact with a page, which has not fully loaded. Another popular problem is lack of an item’s display, because it was waiting, for example, for the server response. However, one of the biggest difficulties connected with the wait’s wrong implementation is the unnecessary waiting, which increases our test duration.
Wait in Selenium
In one of the most popular tools for tests automation, that is Selenium, we encounter three types of waits, which I will describe below.
Implicit wait
It consists of an attempt to find an element in the DOM for a spcidied time if it is not immediately available. The default value of this wait is 0 and when we change this value, the implicit wait lasts until the end of the session.
As presented in the code above, the implementation of this solution is very fast, but it has some limitations. This wait works only with the findElement() method, and that keeps us from waiting to load, for example, the text in the element.
Explicit wait
Thanks to it, we have a possibility to stop the test until our expected condition is met. Checking the condition is triggered with a certain frequency until the timeout expires. This means, that when the condition returns a false value, Selenium will wait and try again.
In the code presented above, we wait until the title of our page gains the expected value. Keep in mind that the wait is now used only at the time of the trigger, and not as previously – globally. Here you will find a list of available expected explicit wait conditions for Selenium.
FluentWait
We wait in an explicit manner for a certain situation for a given number of seconds – for example: the following code for 10 seconds will check if our website has the expected title with the frequency of one second.
Wait in Cypress
Cypress, by default, uses dynamic wait for elements or actions available on the web page used in our automated test. We can distinguish the following ways to handle waits.
Default timeouts
In Cypress there are 6 default timeouts available. Thanks to the built-in re-try mechanism, they perform checks in a defined by us, or preset time, similarly to the explicit wait previously described:
- defaultCommandTimeout – it is the time, in which Cypress tries to get to the element or action while executing most of the commands on the DOM.
- execTimeout – a timeout that defines how long to wait for the system to complete the operations during the cy.exec() command.
- taskTimeout – the maximum time, in which we wait for a task completion during the cy.task() command.
- pageLoadTimeout – waits for the “page transition event”. This event occurs when a user visits or leaves a particular web page. This default timeout also waits for the cy.visit(), cy.go(), cy.reload() commands to execute load events. They are triggered when the entire page and all its contents, i.e. style sheet, scripts, iframes and images are loaded.
- requestTimeout – the maximum time we wait for cy.wait() execution.
- responseTimeout – waits for the execution of cy.request(), cy.wait(), cy.fixture(), cy.getCookie(), cy.getCookies(), cy.setCookie(), cy.clearCookie(), cy.clearCookies() and cy.screenshot() methods.
If we want to overwrite the values for timeouts, we do it in the cypress.json file:
We can also change these times for a given action chain or the method itself by sending the object with an expected time:
In the code above we used implicit subject to create assertion with the .should() method. First, Cypress tries to find the button element within the 20 seconds defined in the object sent {timeout: 20000}, and then performs assertion on that button, to check if it contains the text “Search in Google”. Here, again, waits up to 20 seconds for the text to appear. We can obtain the timeout while executing the cy.get() and should() method, which will negate the entire test.
For the get(), find(), cointains() methods, from my experience, I recommend using a timeout on the action chain in case the wait for an item or its properties is extended. Whereas, the timeout set in cypress.json should be limited to a reasonable minimum to avoid unnecessary lengthening of the tests in case we receive a fail.
In Cypress, we have the possibility to make a similar assertion with an explicit subject by using the .then() method and making an assertion on a JQuery element:
In this case, Cypress first waits up to 20 seconds for the .get() method, and then immediately makes an assertion on the element, omitting the defined defaultCommandTimeout.
Built-in Wait
Cypress has its own implementation of the wait method, which takes up to two arguments:
- One of the first values is a numeric value given in milliseconds. This is the value of time, which elapses between the previous and the next command in the Cypress action chain.
- The next value we can send in place of time is an alias or an alias table. Aliases are a tremendous construct in Cypress that has many uses and a separate article could be written about it. However, for us, in the waits concept, the most important thing is to focus on the possibility to assign an alias to the API call. We do it through the cy.intercept() and the .as() method, where the as method saves an alias, to which we have access with the use of the @ prefix.
- The last, optional, value is the option object, which allows us to overwrite the previously defined default times for requestTimeout and responseTimeout. Additionally, it pro {log: true/false} object.
This code shows the Cypress wait implementation. This method allows us to wait in a specific place in the code for a given time or until an appropriate alias appears.
Creating an alias
In order to properly create a wait for an alias, we need that alias precisely. One method to create it is to look at the queries made by the browser on our test page. Then, we select the last query or the last few queries made, which, with high probability, will show us that the page is loaded.
and manage requests and responses in the network. It allows us to:
- Mock data
- Validate API requests
- And thanks to the combination with cy.wait(), wait for the end of the given API query.
Then, with the .as() method, we can save the tracked request as an alias.
Intercept implementation
The intercept implementation, which we subsequently use in the test, is shown above. Its task is to:
- Visit google.pl webpage.
- Accept cookies.
- Enter “Sii” in the search bar and click on the Enter button.
- Wait for the @finishedGoogleSearch alias.
- Check whether the text “Około” is visible on the loaded page.
I advise avoiding waits for a given time and focus on waiting for aliases. It will make our tests shorter and the code will be more optimized and significantly free of unexpected exceptions.
One of the additional packages for the ESLint is the Cypress ESLint Plugin, which allows us to define the rule “cypress/no-unnecessary-waiting”: “error” and detect these waits while creating automated tests.
Custom waits implementation
In case we need a custom wait, that doesn’t qualify for the previously described cy.wait(), we can use a recursive wait with a condition check with every reference.
Recursion is the reference of a function or definition to itself. It occurs in mathematics (Euclid’s algorithm, Fibonacci sequence), programming and even art (a picture within a picture or placing two mirrors opposite each other – that’s exactly when we get a reflection within a reflection).
But let’s get back to programming and our example. We want to write a wait, which will wait for the expected item to appear on the page.
Wait performs this task in the following order:
- Retrieving the contents of the body with the .get() method.
- Then, changing this object into a JQuery object, calling the .find(locator) method on it and finally .length, which returns 0 if the element is not in the body.
- Waiting in the split time – that is the time after which we want to make another check whether the element meets the given condition.
- Defining totalTime and starting to increase it by a given break time (split) with each subsequent recursive call.
- Checking if totalTime is less than or equal to the timeout. Depending on the result of the condition, it recursively calls the checkForElement() function, or calls cy.get(resultText) to negate the test.
- Finally, calling cy.get(resultsText) when the condition body.find(elementID).length === 0 is not met, which closes the recursion.
It is not a perfect solution of that problem and should be used as a last resort, because we are dealing with a difficulty of code readability and maintainability.
The code above is very hard to debug in Cypress and incompetent use of recursion or loops may result in falling into an infinite loop. endless loop. There is a possibility of its occurrence when mixing synchronous and asynchronous code or when the increment conditions are incorrectly controlled. Eventually, the infinite loop will cause the test error and the browser will stop responding.
It is not a perfect solution of that problem and should be used as a last resort, because we are dealing with a difficulty of code readability and maintainability.
The code above is very hard to debug in Cypress and incompetent use of recursion or loops may result in falling into an infinite loop. endless loop. There is a possibility of its occurrence when mixing synchronous and asynchronous code or when the increment conditions are incorrectly controlled. Eventually, the infinite loop will cause the test error and the browser will stop responding.
cypress-recurse – the package provides a recursive wait until the expected codition returns a True value. In this case, our expected condition is expect(button).to.have.value(‘Szukaj w Google’)}. The method will negate the test if within 30 seconds this condition will not return the True value. The condition itself will be checked with the frequency of 0.5 seconds. Default values for the method are: timeout=4000, limit=15, delay=800.
Selenium vs. Cypress waits
So let’s look for similarities between Selenium waits and Cypress waits.
Let’s start with the implicit wait. We can observe a similar behavior in Cypress – by controlling “defaultCommandTimeout” or the timeout itself, we can wait for an element to appear on the page with the use of the cy.get() method in the time we specify.
Explicit wait in Cypress is quite a controversial topic, because it is supposed to check a condition. Cypress .should(‘be.visible’) method can be treated as the equivalent of the expected condition in Selenium elementIsVisible(element).
However: is the assertion a wait? If we assume that it is, then we can treat all .should() assertions as an explicit wait. I am sure that opinions on this theory are divided, so I leave the answer to this question to your own interpretation 🙂
Let’s not forget the cy.wait(@alias). It is this wait that can be considered a full-fledged explicit. The presented cypress-wait-until-until and cypress-recurse external packages are closer to an explicit, or even a fluent wait, due to the possibility to control the interval according to which the condition is checked.
Summary
In this article, I have presented the available ways of waiting for elements in Selenium and Cypress – waits, which allow you to work freely on most elements. I have presented the difference in time with Cypress assertions. I have prepared a sample code with my own wait and have gone over two external packages that we can work with in Cypress, depending on our needs.
If you are starting your adventure with Cypress, it is worth acquiring knowledge of the cy.wait(‘@Alias’) area from the beginning. It is a basic functionality of the tool, which increases the quality of the code in our automated tests and eliminates the need to wait for the element to load. Good luck!