1. Overview
One of the challenges of web application testing is dealing with the dynamic nature of web pages. Web pages can take time to load, and elements may only appear after some time. As a result, Selenium provides wait mechanisms to help us wait for elements to appear, disappear, or be clickable before continuing test execution.
In this article, we’ll explore the differences between the wait types and how to use them in Selenium. We’ll compare implicit wait vs explicit wait and learn a few best practices when using waits in Selenium tests.
2. Wait Types in Selenium
Selenium provides various wait mechanisms to help wait for an element to appear, disappear, or be clickable. These wait mechanisms can be classified into three types: implicit wait, explicit wait, and fluent wait.
For our test cases, we’ll define a few constants for our page locators that we’ll use to navigate through the web page:
private static final By LOCATOR_ABOUT = By.xpath("//a[starts-with(., 'About')]");
private static final By LOCATOR_ABOUT_BAELDUNG = By.xpath("//h3[normalize-space()='About Baeldung']");
private static final By LOCATOR_ABOUT_HEADER = By.xpath("//h1");
2.1. Implicit Wait
An implicit wait is a global setting that applies to all elements in a Selenium script. It waits a specified time before throwing an exception if the element is not found. We can set the wait once per session and can’t change it later. The default value is 0.
We can set the implicit wait to 10 seconds with the following statement. We should set the implicit wait right after initializing the WebDriver instance:
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
With implicit wait, we don’t need to explicitly wait for anything in our tests.
The following simple test navigates to www.baeldung.com and navigates to the About page via the header menu. As we can see, there are no explicit wait instructions, and the test passes with an implicit wait of 10 seconds:
void givenPage_whenNavigatingWithImplicitWait_ThenOK() {
final String expected = "About Baeldung";
driver.navigate().to("https://www.baeldung.com/");
driver.findElement(LOCATOR_ABOUT).click();
driver.findElement(LOCATOR_ABOUT_BAELDUNG).click();
final String actual = driver.findElement(LOCATOR_ABOUT_HEADER).getText();
assertEquals(expected, actual);
}
It’s important to note that not setting the implicit wait will lead to a failing test.
2.2. Explicit Wait
An explicit wait is a more flexible wait that allows us to wait for a specific condition to be met before continuing test execution.
We can define the condition, such as the presence or absence of an element, using ExpectedConditions class. An exception is thrown if the condition is not met within the specified time.
The polling frequency of the WebDriver to check the expected condition is fixed at 500 ms. As the explicit wait is not set globally, we can use different conditions and timeouts for everything.
Looking at the previous test case, we noted that the test would fail without an implicit wait. We can adapt the test and introduce explicit waits.
First, we’ll create a Wait instance with a timeout of 10 seconds for our test:
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(TIMEOUT));
Now we can use this Wait instance and call until() with the ExpectedConditions. In this scenario, we can use ExpectedConditions.visibilityOfElementLocated(..).
Before interacting with an element, e.g., clicking, we need to wait for the element to be visible or clickable:
void givenPage_whenNavigatingWithExplicitWait_thenOK() {
final String expected = "About Baeldung";
driver.navigate().to("https://www.baeldung.com/");
driver.findElement(LOCATOR_ABOUT).click();
wait.until(ExpectedConditions.visibilityOfElementLocated(LOCATOR_ABOUT_BAELDUNG));
driver.findElement(LOCATOR_ABOUT_BAELDUNG).click();
wait.until(ExpectedConditions.visibilityOfElementLocated(LOCATOR_ABOUT_HEADER));
final String actual = driver.findElement(LOCATOR_ABOUT_HEADER).getText();
assertEquals(expected, actual);
}
We need to manage the waits manually, but we are much more flexible. This can improve the test performance significantly.
The ExpectedConditions class provides many methods we can use for checking expected conditions. Let’s see a few:
- elementToBeClickable()
- invisibilityOf()
- presenceOfElementLocated()
- textToBePresentInElement()
- visibilityOf()
2.3. Fluent Wait
A fluent wait is another type of explicit wait that allows for more fine-grained control over the waiting mechanism. It enables us to define the expected condition and the polling mechanism to check for the particular condition to be met.
We can again adapt the previous test case and introduce fluent waits. Fluent waits are basically explicit waits, so we only have to adapt the Wait instance. We specify the polling frequency:
Wait<WebDriver> wait = new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(TIMEOUT))
.pollingEvery(Duration.ofMillis(POLL_FREQUENCY));
The test itself will stay the same as for the explicit wait:
void givenPage_whenNavigatingWithFluentWait_thenOK() {
final String expected = "About Baeldung";
driver.navigate().to("https://www.baeldung.com/");
driver.findElement(LOCATOR_ABOUT).click();
wait.until(ExpectedConditions.visibilityOfElementLocated(LOCATOR_ABOUT_BAELDUNG));
driver.findElement(LOCATOR_ABOUT_BAELDUNG).click();
wait.until(ExpectedConditions.visibilityOfElementLocated(LOCATOR_ABOUT_HEADER));
final String actual = driver.findElement(LOCATOR_ABOUT_HEADER).getText();
assertEquals(expected, actual);
}
3. Implicit Wait vs. Explicit Wait
The implicit and explicit wait is used to wait for an element to appear on the page. However, there are some differences between them:
- Timeouts: Implicit wait sets a default timeout for the entire test runtime, while explicit wait sets timeouts for specific conditions.
- Condition: Implicit wait waits for an element to appear on the page, while explicit wait waits for a specific condition, such as the presence of an element or the element to be clickable.
- Scope: Implicit wait applies globally, while explicit wait applies locally to a specific element.
- Exception: Implicit wait throws a NoSuchElementException when the WebDriver cannot find the element within the specified timeout. In contrast, explicit wait throws a TimeoutException when the element doesn’t meet the condition within the specified timeout.
The implicit wait is helpful when we want to wait a certain amount of time for elements to appear on the page. However, the explicit wait is the better option if we need to wait for a specific condition.
According to Selenium documentation, we should not mix them:
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.
4. Best Practices
When using waits in Selenium, there are a few best practices to keep in mind:
- Always use waits: Waiting for elements to load is a crucial step in automated testing.
- Use explicit wait instead of implicit wait: Implicit waits can cause our tests to take longer than necessary to fail if the element cannot be located. Explicit and fluent waits are better because they allow us to wait for a specific condition to occur before proceeding further.
- Use fluent waits when necessary: We should use fluent waits when we need to verify a specific condition repeatedly until it becomes true, as they offer greater control over the waiting mechanism.
- Use reasonable wait times: Wait times that are too short can cause false negatives, while wait times that are too long can unnecessarily increase the total execution time of the test.
- Use ExpectedConditions: When using explicit or fluent waits, it’s essential to use the ExpectedConditions class to define the conditions we want to wait for. By doing this, we can ensure that our tests wait for the appropriate conditions and fail quickly if the conditions aren’t met.
5. StaleElementReferenceException
The StaleElementReferenceException is a problem that occurs when the previously located element is not available anymore.
This can happen when the DOM changes after we located the element, e.g., when waiting for the specified condition of the element to be true.
To solve the StaleElementReferenceException, we’ll need to relocate the element whenever this exception occurs. Every time a SERE (StaleElementReferenceException) occurs, we’ll catch the exception, relocate the element and retry the step again. Because we can’t be sure that reloading fixes the problem, we need to stop the retry after a few attempts:
boolean stale = true;
int retries = 0;
while(stale && retries < 5) {
try {
element.click();
stale = false;
} catch (StaleElementReferenceException ex) {
retries++;
}
}
if (stale) {
throw new Exception("Element is still stale after 5 attempts");
}
As this solution is inconvenient when implemented everywhere, we can wrap the WebElement and the WebDriver and handle the StaleElementReferenceException internally.
With this approach, we don’t need to take care of StaleElementReferenceExceptions in our test code. We must wrap the WebDriver to work with the wrapped WebElements. All methods in the WebElement that can throw a StaleElementReferenceException can be adapted.
6. Conclusion
In this tutorial, we learned that waits are an essential part of writing effective and efficient tests in Selenium.
Properly utilizing waits can prevent potential timing problems, ensure the elements load before interacting with them, and decrease the likelihood of test failures.
Explicit waits provide more control over the waiting mechanism than implicit waits. When we need a more fine-grained control, we can use fluent waits to check for a particular condition with a specified frequency until the WebElement meets the specified condition or a timeout occurs.
By following the best practices, we can significantly improve the efficiency and reliability of our automated tests.
As always, the implementation of all these examples can be found on GitHub.