logo logo

Using Selenium WebDriver Waits as Retries in Your Tests

Using Selenium WebDriver Waits as Retries in Your Tests

The topic of Selenium tests failing due to timing issues comes up quite a lot. Random failures are attributed to the interaction being done too early, when the page has either not properly loaded, or when the WebElements themselves were not fully initialized. The biggest issue occurs when JavaScript is involved in rendering page elements.

Although in some cases testers will try to use Selenium WebDriver wait methods to wait for certain conditions to be fulfilled before trying the interaction, they might still get test failures. So what would be a good solution to have reliable tests? 🤔 Read on to see how we can create aggregated wait methods that will allow us to retry complex conditions.

The WebDriverWait class and methods

Before we start writing our aggregated wait methods, we need to look a little bit at the WebDriverWait class from Selenium. It allows us to create custom wait methods in our tests, where we can wait for all sorts of conditions to be fulfilled, like a WebElement to be displayed, or the text on a WebElement to equal a certain String, and so on. When creating your wait methods you might use some of the ExpectedConditions methods that exist in the Selenium library, or you might write some custom code yourself.

Let’s assume that in your project all your wait methods can be found in one class. On the class level, you would define a TIMEOUT constant value. It represents the maximum amount of time in seconds you want a wait method to wait for a condition to be fulfilled.

Now let’s assume you have a wait method in the wait class, which waits up to the TIMEOUT period for a condition to be accomplished. The WebElement on which the condition is checked is passed as a parameter to the method. Some other parameters can also be passed if needed. For example, when waiting for the text of an element, you want to pass the text as a method parameter. In this method, every half a second the condition is evaluated. If the condition evaluates to true, the method will exit successfully. If the condition did not evaluate to true, the condition will be tried again after half a second elapses, unless we have exceeded the TIMEOUT number of seconds.

Great, but where and how will we apply these concepts? 🕵️‍♀️

Example – click on a button and wait for another element to be displayed

Let’s say we want to create a method that waits for an element to be displayed. This method would like this:

public void waitForElementToBeDisplayed(WebElement element) {
    WebDriverWait wait = new WebDriverWait(driver, TIMEOUT);
    ExpectedCondition<Boolean> elementDisplayed = arg0 -> {
        try {
            element.isDisplayed();
            return true;
        } catch (Exception e) {
            return false;
        }
    };
    wait.until(elementDisplayed);
}

Now let’s see a method where we want to click on a WebElement:

public void click(WebElement element) {
        WebDriverWait wait = new WebDriverWait(driver, TIMEOUT);
        ExpectedCondition<Boolean> elementIsClickable = arg0 -> {
            try {
                element.click();
                return true;
            } catch (Exception e) {
                return false;
            }
        };
        wait.until(elementIsClickable);
    }

For both these cases, within the try, we will check whether the condition we are interested in has been accomplished.

  • In the first method, we check whether the element is displayed.
  • In the second method, we check whether clicking on an element works without throwing an Exception. If no Exception has been thrown when the click was attempted, the method exits successfully. However, if any Exception was thrown, after half a second, if the TIMEOUT period was not achieved, the click is attempted again.

📌 Note that the Exceptions that can be thrown in both these wait methods are defined in Selenium and include NoSuchElementException (when the WebElement does not exist) or StaleElementReferenceException (when the WebElement was present in the DOM, but due to a refresh action, it is not present anymore), among others.

The simple variant – no JavaScript, no retry required

Now that we have these methods, we could easily accomplish the task of clicking on a button and waiting for an element to be displayed, by calling the methods one after the other:

waiter.click(theButton);
waiter.waitForElementToBeDisplayed(theElementToBeDisplayed);

In this case, we will wait for up the TIMEOUT period for the click to be successful. This means the click is tried over and over again until no Exception is thrown. That works fine if we are talking about a button whose behavior is not set by JavaScript. A plain HTML button will work perfectly with this wait method. Once the click was successful, the wait method for the presence of a new element is called, and of course, that will execute successfully.

The other variant – with JavaScript, retry required

Now in some cases, the behavior of a button is implemented by means of JavaScript. This button, when clicked, will execute some JavaScript code which will trigger the new element to be displayed. For such a button, the above wait method calls will not always work properly: the first time the click is attempted, it might happen that no Exception will be thrown. Therefore the wait method will exit successfully. However, when looking at the button, you might realize that it looks like the click occurred, but the button kind of looks frozen. It looks like it is clicked, but not released. Of course by this time the waitForElementToBeDisplayed method executes, but it will fail because the ‘frozen’ button did not trigger the appearance of the new element.

In this case, a different approach is required: we will create an aggregate wait method, inside which we will attempt the click, then the waiting for the new element, over and over again.

What we want this method to do: up to the TIMEOUT period, try the click, then the waiting for the element. If any of these 2 actions failed, try again, this time starting with the click, no matter which of the 2 actions failed. Use wait methods for each of these 2 actions.

Also, looking at the existing 2 wait methods, these will both wait up to the TIMEOUT period. Let’s assume that for some reason the click does not work up until the TIMEOUT-1 period, in seconds. That means, because we want our aggregate wait method to wait in total up to the TIMEOUT period, the ‘wait for the element to be displayed’ method will only have 1 second left to run, until the TIMEOUT period expires. This is not efficient ❌👎

Therefore, what we want is: for each of the inner wait methods from the aggregate method to wait up to, let’s say, 5 seconds. Of course, you can tweak this amount of time depending on your test environments, because we will create new wait methods for the click and the waiting for the element to be displayed, which will take a parameter representing the timeout period we are interested in. This way we can call such methods with any value for the number of seconds we would require.

So let’s see what these new methods look like. The first one, the click:

public void waitForElementToBeDisplayed(WebElement element, int timeoutPeriod) {
        WebDriverWait wait = new WebDriverWait(driver, timeoutPeriod);
        ExpectedCondition<Boolean> elementDisplayed = arg0 -> {
            try {
                element.isDisplayed();
                return true;
            } catch (Exception e) {
                return false;
            }
        };
        wait.until(elementDisplayed);
    }

Now, the aggregate method will look like this:

public void clickAndWaitForElementDisplayed(WebElement elementToClick, WebElement elementToWaitFor) {
        WebDriverWait wait = new WebDriverWait(driver, TIMEOUT);
        ExpectedCondition<Boolean> condition = arg0 -> {
            try {
                click(elementToClick, 5);
                waitForElementToBeDisplayed(elementToWaitFor, 5);
                return true;
            } catch (Exception e) {
                return false;
            }
        };
        wait.until(condition);
    }

Let’s see how this works for a plain HTML button: let’s say that the click method is not successful (the timeout period of 5 seconds elapsed, but an Exception was thrown when trying to click the button each time). In this case, after 5 seconds, the click method begins running again. That happens because of the catch branch, where we are catching any Exception. When the click method fails, it throws a TimeoutException, which of course is a specialized type of Exception. Let’s say that after another 3 seconds, the click method manages to click the element without any Exception. Next, the waiting for the element to be displayed is done, for up to 5 seconds. If it is successful, the aggregate wait method exits successfully.

Let’s think about how this works for the button which uses JavaScript. First, the click is attempted. And let’s assume that the click does not throw an Exception, but that the button, after being clicked, is in that frozen status where the new element being displayed is not triggered. In this case, after the first 5 seconds have passed, the click is retried, and if successful, the wait for the new element to be displayed is performed.

Conclusion

Comparing the 2 approaches (the aggregate method vs. simply calling the wait method one after the other) for the JavaScript button, we can easily see how the aggregate method approach is one that actually makes sense. Given that when the button is frozen, no Exception that Selenium recognizes is thrown, unless the retry is attempted, the click wait method considers that the click occurred successfully. However in many cases that is not true.

You can use this retry approach in many other testing scenarios. One of these can be clicking a so-called dropdown and selecting a value from it. This would be a dropdown that is implemented in a different way than a regular HTML dropdown: it requires a click to show its options. Another example would be clearing a text field and then typing into it. For this example, in some cases, clearing does not work properly in the first attempt. Or it does not manage to clear the entire content of the text field before the new one is typed.

Another nice approach comes from TestProject with their Adaptive Wait mechanism, ensuring that all environment conditions are sufficient for an automation action to succeed. TestProject will adapt to the actual application loading pace and execute the next action only once the proper conditions are met ✅ As a failsafe, you can set a maximum wait time before the step fails (by default 15 sec) in the step section, so your test will move on to the next step in the case that the conditions are never met.

What’s your preferred wait method? Share in the comments below! 😉

About the author

Corina Pip

Corina is a Test & Automation Lead, with focus on testing by means of Java, Selenium, TestNG, Spring, Maven, and other cool frameworks and tools. Previous endeavours from her 11+ years testing career include working on navigation devices, in the online gaming industry, in the aviation software and automotive industries.
Apart from work, Corina is a testing blogger (https://imalittletester.com/) and a GitHub contributor (https://github.com/iamalittletester).
She is the creator of a wait based library for Selenium testing (https://github.com/iamalittletester/thewaiter) and creator of “The Little Tester” comic series (https://imalittletester.com/category/comics/). She also tweets at @imalittletester.

Leave a Reply

FacebookLinkedInTwitterEmail