Selenium waits

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 due to Ajax or just some other JavaScript manipulation, the elements might not be immediately available. 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 on a global basis (after creating the driver), per page, or per method; you just need to keep in mind that its value is part of the Selenium state, so it will be kept until it’s changed.

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

Explicit wait

If you want to wait for specific conditions, you can use an explicit wait. 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 a lot of things such as the visibility of elements, some text to be present, the URL has changed 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:

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) is what 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, through 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 to me, 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

--

--

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

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Luís Soares

Luís Soares

513 Followers

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