Introduction
Flaky tests are the bane of any automation engineer’s existence. One of the primary culprits behind flaky tests is improper or insufficient waiting mechanisms. In this article, we’ll dive deep into the waiting strategies provided by WebdriverIO and Java Selenium.
Java Selenium Waits
Java’s Selenium WebDriver offers two primary types of waits: Implicit and Explicit.
1. Implicit Wait
What it does: Automatically waits for a specified amount of time before throwing a NoSuchElementException if the WebDriver cannot find the element on the page. It’s set for the entire duration of the WebDriver instance and is applicable for all elements.
WebDriver driver = new ChromeDriver();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
// waits for a target selector to be displayed
Pros:
Simple to set up. Applies globally to all findElement() and findElements() calls.
Cons:
Not recommended for modern web applications with dynamic content since it’s a static wait. Can slow down tests if the set duration is longer than necessary.
When to use: For setting a baseline wait for the entire test session.
2. Explicit Wait
What it does: Waits for a specific condition to be met (e.g., an element to be clickable, visible, etc.) before proceeding. Uses the WebDriverWait class in combination with the ExpectedConditions class.
WebDriverWait wait = new WebDriverWait(driver, 20);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("elementId")));
Pros:
More dynamic than Implicit Wait. Allows for specific conditions, making it more flexible.
Cons:
Requires more code than Implicit Wait. Specific to the condition and doesn’t apply globally.
When to use: When you need to wait for a specific condition, like an element to be visible, clickable, or present.
3. Fluent Wait
What it does: Fluent Wait, often referred to as the “Smart Wait,” allows you to define the maximum amount of time to wait for a specific condition and the frequency with which to check the condition before throwing an exception. It can ignore specific types of exceptions while waiting, such as NoSuchElementException when searching for an element.
It is similar to Explicit Wait but offers more flexibility. Allows you to specify the polling frequency (how often to check the condition) and which exceptions to ignore during the waiting period.
Usage:
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
.withTimeout(Duration.ofSeconds(30))
.pollingEvery(Duration.ofSeconds(5))
.ignoring(NoSuchElementException.class);
WebElement element = wait.until(new Function<WebDriver, WebElement>() {
public WebElement apply(WebDriver driver) {
return driver.findElement(By.id("elementId"));
}
});
Pros:
Highly customizable. Can define polling frequency and ignore specific exceptions.
Cons:
Requires more code than both Implicit and Explicit Waits. Might be overkill for simple scenarios.
WebdriverIO Waits
1. browser.pause()
What it does: Introduces a hard-coded, fixed delay.
browser.pause(5000); // Pauses for 5 seconds
When to use: For setting a baseline wait for the entire test session.
2. Explicit Wait
What it does: Waits for an element to be visible.
const elem = $('selector');
elem.waitForDisplayed(5000);
// waits for a target selector to be displayed
When to use: When you need an element to be visible before interacting with it.
3. Fluent Wait
In WebdriverIO, when you use methods like waitForDisplayed(), waitForExist(), or waitForClickable(), you’re essentially using a fluent waiting mechanism. These methods will repeatedly check for a condition until either the condition is met or a timeout is reached, which is the core idea behind Fluent Wait.
for example
const elem = $('selector');
elem.waitForDisplayed({ timeout: 5000, interval: 500 });
In this example:
timeout: 5000 specifies that the method should wait up to 5 seconds for the element to be displayed. interval: 500 (if provided) would specify that the method should check the condition every 500 milliseconds. This is similar to how Fluent Wait works in Java’s Selenium, where you specify a timeout and a polling interval.
Furthermore, WebdriverIO’s waitUntil() method provides even more flexibility, allowing you to define custom waiting conditions, similar to the flexibility Fluent Wait offers in Java:
browser.waitUntil(
() => $('selector').getText() === 'Expected Text',
{
timeout: 5000,
timeoutMsg: 'Expected text to be different after 5s'
}
);
So, while WebdriverIO doesn’t label its waiting mechanisms as “Fluent Wait,” the functionality and flexibility are very much present and align with the fluent waiting concept. Now that you learned all this, and did not fall asleep 😃 😃 😃 from this blog where comes the practical part into play.
Common Wait-Related Errors in e.g. Dockerized UI Testing
When running UI tests in Docker containers, several challenges can arise, especially concerning waits. Here are some frequently encountered errors and their potential solutions:
1. Timeout Errors
- Error Messages:
- TimeoutException or
- Wait timed out after [x] milliseconds.
- Description:
- Occurs when a wait condition wasn’t met within the specified timeout period, often due to the application behaving differently or slower inside a container.
2. Element Not Found
- Error Messages:
- NoSuchElementException.
- Description:
- Elements might not be present in the DOM due to slower loading times in containers, even if waits are set.
3. Element Not Interactable
- Error Messages:
- ElementNotInteractableException.
- Description:
- Occurs when trying to interact with an element before it’s ready, such as a button before it’s fully rendered. With NosuchElement exception this is the most common answer I need to explain to students 😃
4. Stale Element Reference
- Error Messages:
- StaleElementReferenceException.
- Description:
- Happens when the DOM changes after getting a reference to an element, but before interacting with it.
5. Network Errors
- Error Messages:
- Network timeouts, connection resets.
- Description:
- More frequent in containerized environments due to network configurations or restrictions.
Solutions and Best Practices:
- Increase Wait Times: Adjust default wait times to account for container unpredictability.
- Use Explicit Waits: Rely on explicit waits that wait for specific conditions rather than implicit waits.
- Health Checks: Ensure services are operational before test execution.
- Logs and Monitoring: Maintain detailed logs for error root cause analysis.
- Environment Parity: Match the Docker environment to production/staging to minimize unexpected behaviors.
- Handle Network Issues: Use utilities to wait for services or increase network timeouts if network-related errors are frequent.
By understanding these common errors and implementing best practices, you can enhance the robustness and reliability of containerized tests.
In Summary
Implicit Wait is a set-it-and-forget-it mechanism but can lead to slower test execution. Explicit Wait is more dynamic and condition-specific, making it suitable for most scenarios in modern web applications. Fluent Wait offers the most flexibility and is best for complex scenarios where you need fine-grained control over the waiting conditions and polling frequency.
Regarding webdriverio if you look at the webdriverio concept through its built-in waiting mechanism they are inherently using fluent wait mechanism constantly if you want to use it.
Properly waiting for elements or conditions is crucial for stable automation tests. Whether you’re using WebdriverIO or Java Selenium, understanding and utilizing the right waiting strategy can make all the difference between a flaky test and a reliable one.
Remember to always avoid arbitrary, hard-coded waits and instead opt for dynamic waits that adapt to the application’s behavior. Happy testing!
by Ralph Van Der Horst