Introduction
I think we can safely say that we have all suffered from this issue. We create tests and the methods required to run them, but elements do not load at the expected time. Sometimes they’re faster, sometimes they’re slower, causing tests to miss a click, a validation, or worse.
That is why we tend to utilize WebDriverWaits when using Selenium, right? But for some reason, in many cases, the waits we define in our code appear to not be working at all and tests fail regardless.
This is a huge (and inevitable?) frustration in the life of an Automation Testing Engineer. This may be the reason why we all tend to implement a bad (however useful in many cases) Thread.sleep to prevent tests from failing, which just hard stops the execution that will allow the window to be displayed, the button to be clicked, the table to be loaded or the service to respond.
This is by no means a good testing strategy. In fact, it is the complete opposite. We all know that stopping the execution like this is not a good practice for many reasons:
- Exceeds tests’ run time provoked by all of those sleeps present in the code.
- Increases time spent on Thread.sleeps when the volume of data in the application grows.
- Leads to slowness, flaky tests, and poor test design.
- Increases use of resources, for example, pipelines, memory, etc.
Despite that, we all know that however bad, the sleep sometimes can be helpful to prevent tests from failing due to different loading times or given how the DOM is constructed. I discourage using them completely, but I will not go so far as to say that you should never ever make use of some Thread.sleeps to help your tests. That is one of the most important points of this article: if you just must use a Thread.sleep, use as few as possible for specific purposes.
For all the other scenarios, let’s use something more sophisticated and elegant that will, in many cases, lower the time spent in our test executions. That alone is a good enough reason to look into FluentWaits, the focus of this article.
As a quick note, all the code presented here is a mere example of some situations and the code will be written in Java.
What is FluentWait
How many times have we seen this kind of situation?
wait.until(ExpectedConditions.visibilityOf(someElement));
Only to feel depressed when the test fails because “someElement” is not visible and the test crashes horribly even though the wait condition is there. This is a typical situation where you might consider changing the wait for a Thread.sleep. However, with FluentWait we are given new hope on the horizon .
So, let’s first define what FluentWait is a little more. From Selenium’s documentation, we get the following definition:
“An implementation of the Wait interface that may have its timeout and polling interval configured on the fly.
Each FluentWait instance defines the maximum amount of time to wait for a condition, as well as the frequency with which to check the condition. Furthermore, the user may configure the wait to ignore specific types of exceptions whilst waiting, such as NoSuchElementExceptions when searching for an element on the page.”
source: Selenium FluentWait
This little piece of information is telling us that a FluentWait is just another implementation of the wait class that can be configured. We can set the timeout, the polling interval, and the exceptions that will be ignored. A basic FluentWait is declared as follows:
// Waiting 30 seconds for an element to be present on the page, checking
// for its presence once every 5 seconds.
Wait wait = new FluentWait(driver)
.withTimeout(30, SECONDS)
.pollingEvery(5, SECONDS)
.ignoring(NoSuchElementException.class);
This is not that different from how we define a WebDriverWait:
WebElement wait = new WebDriverWait(driver, 30)
The most noticeable difference is that the FluentWait has a polling mechanism in which the condition will be revisited until the timeout is completed or the expected condition is met.
Also, we can add a list of exceptions we would like to ignore. When one of those exceptions occurs, it will not stop waiting for conditions but will skip the remaining statements.
So, on paper, the FluentWait may seem like a better approach than Thread.sleeps spread across the entire framework, right? In general, it is. But make no mistake here, the FluentWait is helpful and elegant in most cases but it is not perfect, as we will see in the next section.
The bad
Once you define the basic configuration of the FluentWait you would like to use and replace all your Thread.sleeps from your framework for waits with the necessary conditions, you are now ready to run your tests.
At first, many tests may run as expected and the overall feel is that the browser is actually expecting the conditions you defined to be met to continue with the execution flow. That might give you the confidence to replace more and more tests with waits instead of sleeps, and this is when some problems start to appear.
When more FluentWait conditions are implemented, more errors and exceptions will start appearing in your test executions. This does not allow the framework to be robust enough or even stable. However, many of these situations will be controlled and mitigated with the proper use of tries and catch (we’ll discuss a little bit about these later).
This pile of exceptions is far from being ideal situation. You will most likely spend hours and hours figuring out what is actually making your test break.To be honest, there will be times when you won’t be able to completely remove those sleeps.
This is a rather long process and it takes a lot of effort to start changing the mindset to use a more convenient way of handling load times in the application without the usual Thread.sleep(). The goal ahead is much more appealing and helpful than you might think. So, with that in mind, let’s move forward.
Exceptions everywhere
Yes, exceptions! You might not be all that familiar with this word, but you will be now. As soon as you start replacing Thread.sleeps for FluentWait conditions, they will start appearing all over the console in every one of the test executions you performed. This not necessarily a bad thing This just means that there are some interactions and conditions that your code is not robust enough to handle, including:
- Timeouts
- One element on top of another
- Different loading speeds
- Element not being interactable
- Stalled element or function
- Etc.
These are just some of the exceptions that you may (most likely will) encounter when starting your new executions free of Thread.sleeps. So, more and more often you will see that the likeness of your code will slightly change to something like this:
public void methodOne() {
wait.until(ExpectedConditions.visibilityOf(someField));
try {
someField.sendKeys(value);
} catch (ElementClickInterceptedException exception) {
Logger.printError("Another element prevented this element to be clicked");
// The exception is catched, log and the execution continues
someOtherElement.click();
}
}
This is just a simple example of how the methods used with the application under testing will look like after you implement Selenium’s FluentWaits. As you continue refactoring your code, this kind of situation will become more normal and you will be well prepared for dealing with any kind of exception thrown at you.
Conclusion
The FluentWait exists with the sole purpose of helping us deal with one of the biggest (and still not completely resolved) issues in the world of automation:to interact with the application in a more efficient way. As many of our tests depend on the elements to be loaded, displayed, or having certain qualities, we need to find a way to mitigate this. Furthermore, the problem becomes even greater when data loading or the volume of data handled by the application is huge or uses a third-party application that we do not have access to or control over.
So the list of problems that can be solved with Thread.sleep() is huge. But we all know that it is not the answer (or at least the correct one) and the FluentWait does its part in helping us with those same issues.
The overall experience when using FluentWait is that the tests are more performant, and you can reduce a lot of time just by removing a couple of sleeps. The tests are more robust, as they are now prepared to deal with exceptions and unexpected errors, and the execution can continue despite them.
If you really are interested in making your tests more time efficient and less prone to breaking, give this a try and then just wait and see.