To appreciate why selenium explicit waits can be problematic, we must first understand their context in the evolution of test synchronization. Early web applications were simpler, with page loads that were largely synchronous. However, with the rise of AJAX, Single Page Applications (SPAs), and third-party scripts, the web became a dynamic and unpredictable environment. A test script attempting to interact with an element might fail simply because the element hadn't been rendered by the JavaScript framework yet.
This gave rise to the most primitive solution: the hard-coded wait.
The Era of Static Waits: Thread.sleep()
The initial, and most naive, approach was to simply pause the test execution for a fixed duration.
// Anti-pattern: Hard-coded wait
driver.findElement(By.id("username")).sendKeys("user");
Thread.sleep(5000); // Wait 5 seconds for the next element to appear
driver.findElement(By.id("submit")).click();
This is universally condemned for two primary reasons. First, it's inefficient. If the element appears in 500 milliseconds, the test still wastes 4.5 seconds. Across a suite of hundreds of tests, this adds up to hours of wasted execution time. Second, it's unreliable. If the network is slow or the server is under load, the element might take 6 seconds to appear, causing the test to fail. As noted in analysis on non-deterministic tests, this kind of fixed timing is a primary source of flakiness.
A Small Step Forward: Implicit Waits
Selenium introduced the implicit wait as a global setting. You configure it once per driver session, and it tells WebDriver to poll the DOM for a certain amount of time when trying to find any element.
// A global setting
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
// This will now wait up to 10 seconds before throwing an exception
driver.findElement(By.id("submit")).click();
This was an improvement. It eliminated fixed pauses and only waited as long as necessary. However, its global nature is a double-edged sword. It applies to every findElement
call, which can hide performance issues in the application. An element that should appear instantly might be taking 8 seconds to load, but the test will pass silently. Furthermore, it only covers one condition: the presence of an element in the DOM. It doesn't help if the element is present but not visible or clickable, a common scenario detailed in the W3C WebDriver specification.
The Reigning Champion: Selenium Explicit Waits
This brings us to selenium explicit waits. They are local, specific, and conditional. Using the WebDriverWait
class in conjunction with ExpectedConditions
, you can instruct the driver to wait for a very specific state before proceeding.
// The 'correct' way in traditional Selenium
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement submitButton = wait.until(ExpectedConditions.elementToBeClickable(By.id("submit")));
submitButton.click();
This approach is granular and powerful. You can wait for visibility, clickability, the presence of text, a specific number of elements, and more. For over a decade, this has been hailed as the definitive best practice for handling synchronization in Selenium. The official Selenium documentation itself promotes this as the most effective strategy. This solved the major problems of the previous methods, leading to more stable tests. But as frameworks and applications have grown in complexity, the limitations of this approach have become a significant architectural burden.