Stop Using Thread.sleep: Mastering Explicit Waits in Selenium
Stop using Thread.sleep() to fix flaky Selenium tests. Learn how to implement WebDriverWait and ExpectedConditions to synchronize your tests with dynamic web content efficiently.
13 Feb 2026, 19:05 UTC

The Flakiness of Arbitrary Pauses
One of the most common causes of "flaky" tests—tests that pass on a developer's machine but fail in CI/CD—is a synchronization mismatch. Modern web applications use asynchronous JavaScript to load content, render components, or enable buttons after an API call completes. When a Selenium script attempts to click an element that hasn't fully rendered or is still disabled, it throws an ElementNotInteractableException or NoSuchElementException.
The instinctive reaction is often to insert Thread.sleep(5000). This is a dangerous habit. Fixed sleeps either waste time (if the element appears in 1 second) or fail anyway (if the network lag pushes the load time to 6 seconds). The solution is the Explicit Wait: a dynamic synchronization mechanism that polls the browser and proceeds the millisecond a specific condition is met.
How WebDriverWait Actually Works
An explicit wait uses the WebDriverWait class in conjunction with ExpectedConditions. Unlike an implicit wait—which is a global setting that tells the driver to poll for any element for a set time—an explicit wait is targeted. It applies to a specific element and a specific state.
When you invoke an explicit wait, Selenium enters a loop. It checks the DOM for the condition (e.g., is the element visible?), and if it fails, it pauses for a short interval (defaulting to 500ms) before trying again. This continues until either the condition returns true or the maximum timeout is reached, at which point it throws a TimeoutException.
Implementation Example: Handling a Delayed Button
Consider a scenario where a "Submit" button remains disabled until a form validation script finishes running. Using a fixed sleep would be inefficient; using an explicit wait ensures the test runs as fast as the application allows.
// Required Imports (Java)
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import java.time.Duration;
// 1. Initialize the wait object with a 10-second timeout
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// 2. Define the condition and wait for it to be met
// This will poll every 500ms and proceed immediately once the button is clickable
WebElement submitButton = wait.until(
ExpectedConditions.elementToBeClickable(By.id("submit-btn"))
);
// 3. Perform the action
submitButton.click();
Execution Context: Run this code within your test method. Ensure you have the selenium-java dependency in your pom.xml or build.gradle. The Duration.ofSeconds(10) acts as a safety ceiling; if the button doesn't become clickable within 10 seconds, the test fails explicitly, providing a clear signal that the application is hanging or broken.
The Critical Trade-off: Explicit vs. Implicit
While explicit waits are powerful, they introduce a small amount of boilerplate code compared to the single line required for an implicit wait. However, the most significant risk is mixing the two.
Selenium documentation warns against combining implicit and explicit waits. If you have a global implicit wait of 10 seconds and an explicit wait of 10 seconds, the actual timeout behavior becomes unpredictable. The driver may wait for the sum of both, or the implicit wait may interfere with the explicit polling logic, leading to tests that hang for much longer than intended.
Comparison Summary
| Feature | Implicit Wait | Explicit Wait |
|---|---|---|
| Scope | Global (all elements) | Local (specific element/condition) |
| Condition | Presence only | Visibility, Clickability, Text presence, etc. |
| Performance | Can slow down "element not found" checks | Proceeds immediately upon success |
Practical Verification
To verify your explicit waits are working correctly, perform these three checks:
- Timing Check: Set a timeout of 10 seconds for an element that appears in 2 seconds. If the test proceeds after 2 seconds (and not 10), the wait is functioning dynamically.
- Failure Check: Temporarily change the element ID in your
By.id()call to something non-existent. The test should fail exactly at the timeout limit with aTimeoutException. - State Check: Use
elementToBeClickableinstead ofpresenceOfElementLocatedfor buttons. A button can be present in the DOM but still disabled; the former ensures the test doesn't attempt to click a greyed-out element.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.