Explicit Wait with Selenium

Recently I started working with Appium for testing our mobile applications. Since we are developing applications for both Android and iOS, Appium seemed like a logical choice. To make my automation more effective, I have decided to write my own element search mechanism that would allow user to specify element search methods in a predefined order. Here's a small sample of how it works:

long tenSeconds = 10000;
ElementSearcher searcher = new ElementSearcher.Builder(driver).
    searchBy(By.id("someId")).
    searchBy(By.name("someName")).
    searchBy(By.xpath("//tag[@attribute='value']")).
    build();
WebElement result = searcher.runSearch(tenSeconds);
Assert.assertNotNull("Element was not found", result);

My initial approach looked something among the lines of:

public WebElement runSearch(long timeoutInMs) {
    long endTime = System.currentTimeMillis() + timeoutInMs;
    WebElement we = null;
    while ((System.currentTimeMillis() <= endTime) && (we == null)) {
        for (By searchMethod : searchMethods) {
            we = driver.findElement(searchMethod);
            if (we != null) {
                break;
            }
            delay(500);
        }
    }
 
    return we;
}

Sadly this approach proved to be very ineffective and my search was running a lot longer than what I expected. Then, after doing more research on waits with Selenium WebDriver, I stumbled upon Explicit Waits.

In a word, here's how it works:
You specify a condition and Explicit Wait mechanism is going to run until your condition either met or your specified timeout expires (in which case you'll end up with a TimeoutException). There are couple of ways you can use explicit waits with Selenium:
Using an ExpetedCondition interface or building out your own mechanism.

First, let's cover the ExpectedCondition. Off the shelf, selenium comes with bunch of predefined conditions such as titleContains, elementToBeClickable, etc. You can get a comprehensive list by visiting selenium's API docs: here.
An example of this approach would look something like this:

import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
...
...
int timeout = 10; // timeout in seconds
new WebDriverWait(driver, timeout).until(ExpectedConditions.titleContains("Google"));

or, if you'd like to specify your own condition, it could look like this:

import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
...
...
int timeout = 10; // timeout in seconds
WebDriverWait wait = new WebDriverWait(driver, timeout);
ExpectedCondition<Boolean> condition = new ExpectedCondition<Boolean>() {
    @Override
    public Boolean apply(WebDriver input) {
        WebElment elment = input.findElement(By.id("someId"));
        return element != null;
    }
}; 
 
wait.until(condition);

Second solution would be to create your own mechanism. Again, this could be done in at least 2 ways: just like the second example of the first approach and using a final variable and use it in the apply method.

import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
...
...
int timeout = 10; // timeout in seconds
WebDriverWait wait = new WebDriverWait(driver, timeout);
 
final ElementSearcher searcher = new ElementSearcher.Builder(driver).
    searchBy(By.id("someId")).
    searchBy(By.name("someName")).
    searchBy(By.xpath("//tag[@attribute='value']")).
    build();
 
ExpectedCondition<Boolean> condition = new ExpectedCondition<Boolean>() {
    @Override
    public Boolean apply(WebDriver input) {
        WebElment elment = searcher.runSearch();
        return element != null;
    }
}; 
 
wait.until(condition);

Or, if you want another approach (either more difficult or cleaner :)), you can create a FluentWait object that would do exactly what you want, you can end up with something like this:

import java.util.concurrent.TimeUnit;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.support.ui.FluentWait;
...
...
long timeoutMs = 10000; // 10 seconds
FluentWait<ElementSearcher> wait = new FluentWait<ElementSearcher>(this).withTimeout(timeoutMs, TimeUnit.MILLISECONDS);
 
WebElement element = null;
try {
    element = wait.until(new Function<ElementSearcher, WebElement>() {
        @Override
        public WebElement apply(ElementSearcher searcher) {
            WebElement element = searcher.runSearch();
            return element;
        }
    });
}
catch (TimeoutException e) {
    logger.fatal("Could not find element within " + timeoutMs + " ms");
}
 
return element;

Now, let's look at the code above in more detail:
On line 1, I am creating a new FluentWait object that is going to do something until my timeout expires. Then, in the try/catch block, I am creating a new Function object that is going to perform an action until action's return value is not null or timeout expires. In my case, the action to be performed comes from a class ElementSearcher which has a method called runSearch (which is a re-write of my original approach above).

The performance with this approach is great! Timeouts work very well and my automated scripts are running much faster!

Hopefully this helps! Happy searching :)

Comments