Lesson 4 of 8 · 10 min
Waits: handling slow and dynamic pages
Web pages do not change instantly. After you click a button, results may appear after one second or five. If your test looks for the element too early, it fails with NoSuchElementException, even though the page is fine. Waits fix this timing problem. Most flaky tests in real projects come from missing or wrong waits.
Why Thread.sleep is a bad idea
The easy fix is Thread.sleep(5000). It pauses the test for exactly five seconds. This has two problems. If the page is ready in one second, you still lose four seconds on every run, and with hundreds of tests this adds up to hours. If the page is slow one day and needs six seconds, the test fails anyway. Selenium offers waits that continue as soon as the condition is true.
Explicit wait
An explicit wait polls for one specific condition and moves on the moment it is true. If the time limit passes first, it throws TimeoutException.
import java.time.Duration;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
driver.findElement(By.id("slow")).click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
WebElement late = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.id("late")));
System.out.println(late.getText());LoadedIn Selenium 4 you give the time as a Duration. Useful ExpectedConditions:
- visibilityOfElementLocated(by): the element exists and is visible.
- elementToBeClickable(by): visible and enabled, so a click will work.
- presenceOfElementLocated(by): the element is in the page, even if hidden.
- alertIsPresent(): a JavaScript alert is open.
- titleIs(text) and urlContains(text): check the page title or address.
- invisibilityOfElementLocated(by): for example a loading spinner has gone.
If the condition never happens, you get a clear failure. The message starts like this:
org.openqa.selenium.TimeoutException: Expected condition failed: waiting for visibility of element found by By.id: late, but... org.openqa.selenium.NoSuchElementException: no such element: Unable to locate element: {"method":"css selector","selector":"#late"}.
(tried for 2 seconds with 500 milliseconds interval)Implicit wait
An implicit wait is one global setting for the whole driver. Every findElement call will retry for up to that time before it gives up. The default is 0, so by default a missing element fails at once.
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
driver.findElement(By.id("slow")).click();
System.out.println(driver.findElement(By.id("late")).getText());LoadedIt looks simple, but it only waits for existence, not for visibility or clickability. The Selenium documentation also warns: do not mix implicit and explicit waits, because the total wait time becomes unpredictable. Choose explicit waits for real projects.
FluentWait
FluentWait lets you choose the polling interval and which exceptions to ignore while polling.
import org.openqa.selenium.NoSuchElementException; // not java.util
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
Wait<WebDriver> fluent = new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(3))
.pollingEvery(Duration.ofMillis(300))
.ignoring(NoSuchElementException.class);
WebElement e = fluent.until(d -> d.findElement(By.id("late")));Try it yourself
- Make a page where a button adds an element after 2 seconds using setTimeout. Click it and read the new element using an explicit wait.
- Replace the wait with Thread.sleep(500) and observe the failure.
- Set a wait time of 1 second for an element that takes 3 seconds, and read the TimeoutException message.
- Write a helper method waitForVisible(By by) that you can reuse in all tests.