Stop Flaky Selenium Tests with Explicit Waits
Replace Thread.sleep with WebDriverWait and ExpectedConditions to synchronize Selenium tests with dynamic UI updates.
02 Feb 2026, 21:34 UTC

The flaky test that passes on your machine
You click a "Save" button. The application sends an AJAX request, shows a spinner, and then renders a confirmation banner. Your Selenium test clicks the button and immediately looks for the banner. Sometimes the banner is there; sometimes the test fails with NoSuchElementException. The difference is network speed, not test logic.
Many teams patch this with Thread.sleep(2000). That is a fixed pause, not synchronization. If the page responds in 300 ms, you waste time. If it needs 2.5 seconds, the test still fails. The useful takeaway is to wait for a specific condition to become true, not for a fixed number of milliseconds.
What an explicit wait actually does
Selenium’s WebDriverWait polls the DOM at a configurable interval until an ExpectedCondition returns true or a timeout expires. An expected condition is a predicate that inspects page state—for example, whether an element is present, visible, or clickable. Unlike an implicit wait, which applies globally to every element lookup, an explicit wait is tied to a single lookup or assertion.
Common built-in conditions include elementToBeClickable, visibilityOfElementLocated, and presenceOfElementLocated. You can also write a custom condition to wait for application-specific states, such as a spinner disappearing or a JavaScript variable changing.
Worked example: wait for a confirmation banner
This example assumes Selenium 4.x, where WebDriverWait accepts java.time.Duration. Imagine a page where clicking #save-btn triggers an AJAX save and reveals #confirmation-banner. The brittle version looks like this:
// Brittle: fixed sleep, race condition remains
driver.findElement(By.id("save-btn")).click();
Thread.sleep(2000);
WebElement banner = driver.findElement(By.id("confirmation-banner"));
assert banner.isDisplayed();
The explicit-wait version waits only as long as necessary:
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
WebDriver driver = ...;
WebElement saveButton = driver.findElement(By.id("save-btn"));
saveButton.click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement banner = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("confirmation-banner")));
if (!banner.isDisplayed()) {
throw new AssertionError("Confirmation banner did not appear");
}
Where to run: in your test project’s source tree, then execute with your normal test runner—for example, mvn test -Dtest=SaveFlowTest from the project root. Permissions: standard read/write access to the project and a compatible browser driver on PATH. Expected check: the test passes repeatedly without manual timing changes. Risk: if the locator is wrong or the banner never appears, the test fails after the timeout rather than immediately, which can slow down failure feedback.
Trade-offs and limitations
- Explicit waits synchronize timing; they do not fix bad locators, missing assertions, or broken application behavior.
- Long timeouts mask performance regressions and make CI slower when a test genuinely fails.
- Do not combine implicit waits and explicit waits carelessly. Selenium’s wait behaviors can stack in ways that make effective timeouts unpredictable.
- An explicit wait applies to the condition you specify. It does not guarantee that unrelated elements are ready.
How to verify the change
- Run the same test five to ten times in a row. It should pass consistently without adjusting the sleep duration.
- Reduce the timeout to a value slightly above the expected response time. If the test starts failing, you have found a real performance issue rather than a flaky timing issue.
- Confirm you are not setting an implicit wait elsewhere in the same driver session. If you are, remove it or document the interaction.
If you need to undo the change, revert the test file to the previous version; there is no database or production state to roll back.
Explicit waits turn timing guesses into verifiable conditions. Start with the slowest, flakiest test in your suite, replace its Thread.sleep with WebDriverWait, and measure whether the failure rate drops.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.