Selenium waits

Waiting for things to happen can be a source of slow and flaky tests. Let’s learn how to wait with Selenium (Kotlin) properly.

Luís Soares
4 min readOct 14, 2022
Photo by Artem Maltsev on Unsplash

Implicit wait

In Selenium, you can query a page with:

// wait until it's found, else throw a timeout exception
driver.findElement(By.id("4-door"))

// wait until at least one element is found, else return empty list when the the time out is reached
driver.findElements(By.tagName("mat-option"))

The problem is that the elements may not be immediately available due to Ajax or JavaScript manipulation. One solution is to rely on implicit waits, where the driver automatically polls the page until an element is found or a timeout is reached (in which case, a runtime exception is raised).

Note that by default, there are no implicit waits at all. You need to set it up once with the following:

driver.manage().timeouts().implicitlyWait(ofSeconds(5))
// Javadoc: specifies the amount of time the driver should wait when searching for an element if it is not immediately present.

You can set up the implicit wait globally (after creating the driver), per page, or method; remember that its value is part of the Selenium state and will be kept until it’s changed.

This is the wait mechanism you should use by default until proven otherwise. Since it’s implicit, you don’t do any extra code. You trust the driver to do it for you.

Explicit wait

You can use an explicit wait if you want to wait for specific conditions. To do it, you set up a wait on the fly with:

// the second argument is the timeout
WebDriverWait(driver, ofSeconds(5))
.until(numberOfElementsToBe(By.cssSelector("mat-option"), 0))
WebDriverWait(driver, ofSeconds(5))
.until(urlContains("/license/draft"))

In pseudocode, the explicit wait is equivalent to:

predicate = false
passedTime = 0
do:
if passedTime > timeout:
raise TimeoutException()
predicate = runPredicate()
sleep('50 miliseconds')
passedTime += 50
while(!predicate)

You can wait for many things, such as the visibility of elements, some text to be present, the URL to change to something, etc. Check all the possibilities with your IDE by typing ExpectedConditions and analyzing the auto-complete (or just checking the docs).

I recommend setting up the polling interval as well to make your tests faster (since the default is half a second):

WebDriverWait(driver, ofSeconds(5))
.pollingEvery(ofMillis(50))
.until(elementToBeClickable(By.cssSelector(".mx-0")))
.click()

/️️/ notice that the wait returns the element, so you can chain a click operation

📝 Some tutorials argue that you can use FluentWait, but WebDriverWait inherits from it, so it has the same functionality.

Explicit wait with a custom predicate

If the native Selenium predicates are not enough (which is rare), you can pass your function to until:

val age = WebDriverWait(driver, ofSeconds(5))
.pollingEvery(ofMillis(50))
.until {
driver.findElement(By.id("person")).getAttribute("age")
}

/️️/ Selenium keeps polling while the provided function returns null (or throws an exception). In the case above, it stops waiting after #person has attribute age.

⚠️ Beware of two timeouts

Beware that a double timeout may happen in the example above: if there's an implicit timeout set up, it can be summed up with the explicit polling interval. Quoting the official docs:

Warning: Do not mix implicit and explicit waits. Doing so can cause unpredictable wait times. For example, setting an implicit wait of 10 seconds and an explicit wait of 15 seconds could cause a timeout to occur after 20 seconds. Waits | Selenium

To avoid it, you should double-check if there’s a native predicate (under ExpectedConditions) that can help you (previous section). If not, you can use this snippet to turn off the implicit timeout temporarily:

fun <T> WebDriverWait.untilWithoutImplicitWait(isTrue: (WebDriver) -> T): T =
until {
val storedImplicitWait = it.manage().timeouts().implicitWaitTimeout
it.manage().timeouts().implicitlyWait(ofSeconds(0))
val result = isTrue(it)
it.manage().timeouts().implicitlyWait(storedImplicitWait)
result
}
// to use it:
val age = WebDriverWait(driver, ofSeconds(5))
.pollingEvery(ofMillis(50))
.untilWithoutImplicitWait {
driver.findElement(By.id("person")).getAttribute("age")
}

Preceding actions

Since you are providing a custom function as a predicate, you can do actions before it. This action happens once per every poll iteration, and it's useful to help reach the desired state:

/️️/ We’re executing a click and verifying if we found what we wanted right after it:
WebDriverWait(driver, ofSeconds(5))
.pollingEvery(ofMillis(50))
.until {
// preceding action:
driver.findElements(By.cssSelector("mat-form-field"))
.first { it.text.contains(placeholder) }
.click()
// predicate:
driver.findElements(By.tagName("mat-option"))
.first { it.text == text }
}
.click()

This mechanism can also be useful to add some logs, so you know how many polls happened.

Sleeping ⛔️

The infamous Thread.sleep(seconds) led me to write this article. Sleeping for a fixed period is never needed. It’s an antipattern because it makes your tests slow if you pick high values (imagine dozens of tests with hundreds of “just 2 seconds” waits). You can argue that you can tune down the sleep times, but then the tests become flaky because sometimes, you need more sleeping time.

Ironically, Thread.sleep(seconds) is exactly what happens in the implicit/ explicit waits. The difference is that they do it in tiny iterations, polling the page until an element is found or a threshold is reached.

Conclusion

Note that you should use the waits in the order they were presented:

  1. set up the implicit wait and, therefore, don’t do any explicit wait by default;
  2. use an explicit wait if you need it;
  3. provide a custom predicate if Selenium doesn’t have the one you need or you need to act per each polling;
  4. don’t use fixed sleeping.

Sometimes, it can be hard to know what to wait for. When that happens, I always “think as a user”. What is the user waiting for in real life? Probably, there’s a condition for that. If not, I have to provide my own. This way, my tests are never coupled with technical details like Ajax calls.

Learn more

--

--

Luís Soares

I write about automated testing, Lean, TDD, CI/CD, trunk-based dev., user-centric dev, domain-centric arch, coding good practices,