Series Navigation
← Part 5: WebElement Interactions
→ Part 7: TestNG Integration — Annotations, Groups and Parallel Runs
Why Waits Exist
Modern web apps are asynchronous. When you click a button, the response might arrive 200ms later or 3 seconds later depending on network conditions. Selenium doesn't know to wait — it tries to find the next element immediately and throws NoSuchElementException.
User clicks Login
│
▼
Browser sends request ──── network ────► Server
│ processes
▼
Browser receives response ◄─── network ─── Response
│
▼
Page updates (AJAX, React re-render, etc.)
│
▼
Element is now available ← Selenium should wait until HERE
Without proper waits, Selenium checks for the element before the page updates.
The Wrong Way — Thread.sleep()
// ❌ NEVER do this in production tests
driver.findElement(By.id("login")).click();
Thread.sleep(3000); // blindly wait 3 seconds
driver.findElement(By.id("dashboard")); // hope it's ready
// Problems:
// 1. Wastes time — if page loads in 0.5s, you still wait 3 full seconds
// 2. Still flaky — if page takes 4s, test still fails
// 3. Scales badly — 100 tests × 3s each = 5 minutes of pure sleeping
Implicit Wait — Set Once, Apply Everywhere
Implicit wait tells WebDriver to poll the DOM for a certain duration before throwing NoSuchElementException:
import java.util.concurrent.TimeUnit;
// Set once in BaseTest setUp() — applies to every findElement() call
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
// Now this will retry for up to 10 seconds before failing
WebElement element = driver.findElement(By.id("dynamicContent"));
How it works:
findElement("dynamicContent") called
│
▼
Element found? → Yes → Return element immediately
│
No → Wait 500ms → Try again
│
No → Wait 500ms → Try again
...
│
No → 10 seconds elapsed → throw NoSuchElementException
Implicit wait problems:
- Applies to EVERY
findElement()call, includingfindElements()used to check if element exists - Makes negative assertions slow (checking element is NOT present waits full timeout)
- Interacts badly with explicit waits — never mix them
Rule: If you use implicit wait, use it consistently and never mix with explicit waits.
Explicit Wait — Precise and Powerful
Explicit waits wait for a specific condition before proceeding. This is the recommended approach:
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
// Create wait with 10 second timeout
WebDriverWait wait = new WebDriverWait(driver, 10);
// Wait until element is visible
WebElement element = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.id("dynamicContent"))
);
element.click();
// Wait until element is clickable (visible + enabled)
WebElement button = wait.until(
ExpectedConditions.elementToBeClickable(By.id("submitBtn"))
);
button.click();
The explicit wait polls every 500ms by default and throws TimeoutException if condition isn't met within the timeout.
All ExpectedConditions — Complete Reference
WebDriverWait wait = new WebDriverWait(driver, 15);
// ── ELEMENT PRESENCE ─────────────────────────────────────────────────────
// Checks DOM only — element may not be visible
WebElement el = wait.until(
ExpectedConditions.presenceOfElementLocated(By.id("myEl"))
);
// Multiple elements present
List<WebElement> els = wait.until(
ExpectedConditions.presenceOfAllElementsLocatedBy(By.cssSelector(".item"))
);
// ── ELEMENT VISIBILITY ───────────────────────────────────────────────────
// Element in DOM AND visible (not hidden)
WebElement visEl = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.id("myEl"))
);
// Visibility of already-found element
WebElement found = driver.findElement(By.id("myEl"));
wait.until(ExpectedConditions.visibilityOf(found));
// All elements visible
List<WebElement> visEls = wait.until(
ExpectedConditions.visibilityOfAllElementsLocatedBy(By.cssSelector(".row"))
);
// ── ELEMENT CLICKABILITY ─────────────────────────────────────────────────
// Visible AND enabled (not disabled)
wait.until(ExpectedConditions.elementToBeClickable(By.id("submitBtn")));
// ── ELEMENT INVISIBILITY ─────────────────────────────────────────────────
// Wait for element to disappear (loading spinner, modal overlay)
wait.until(ExpectedConditions.invisibilityOfElementLocated(By.id("loadingSpinner")));
wait.until(ExpectedConditions.invisibilityOf(spinnerElement));
// ── TEXT CONDITIONS ──────────────────────────────────────────────────────
// Wait for exact text
wait.until(ExpectedConditions.textToBe(By.id("status"), "Complete"));
// Wait for text to contain substring
wait.until(ExpectedConditions.textToBePresentInElementLocated(
By.id("message"), "Success"
));
// Wait for input value
wait.until(ExpectedConditions.textToBePresentInElementValue(
By.id("emailField"), "user@"
));
// ── URL CONDITIONS ───────────────────────────────────────────────────────
wait.until(ExpectedConditions.urlContains("/dashboard"));
wait.until(ExpectedConditions.urlMatches(".*\\/dashboard\\?tab=overview.*"));
wait.until(ExpectedConditions.urlToBe("https://app.example.com/home"));
// ── TITLE CONDITIONS ─────────────────────────────────────────────────────
wait.until(ExpectedConditions.titleContains("Dashboard"));
wait.until(ExpectedConditions.titleIs("My App - Dashboard"));
// ── FRAME CONDITIONS ─────────────────────────────────────────────────────
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt("iframeName"));
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(By.id("paymentFrame")));
// ── ALERT CONDITIONS ─────────────────────────────────────────────────────
wait.until(ExpectedConditions.alertIsPresent());
// ── ATTRIBUTE CONDITIONS ─────────────────────────────────────────────────
wait.until(ExpectedConditions.attributeContains(
By.id("statusLabel"), "class", "success"
));
wait.until(ExpectedConditions.attributeToBe(
By.id("progressBar"), "aria-valuenow", "100"
));
// ── SELECTION CONDITIONS ─────────────────────────────────────────────────
wait.until(ExpectedConditions.elementToBeSelected(By.id("checkbox")));
wait.until(ExpectedConditions.elementSelectionStateToBe(
By.id("checkbox"), true
));
// ── STALENESS ────────────────────────────────────────────────────────────
// Wait for old element reference to go stale (page refreshed/re-rendered)
WebElement oldRef = driver.findElement(By.id("content"));
wait.until(ExpectedConditions.stalenessOf(oldRef));
// Then find the fresh element
WebElement newRef = driver.findElement(By.id("content"));
// ── NUMBER OF ELEMENTS ───────────────────────────────────────────────────
wait.until(ExpectedConditions.numberOfElementsToBe(By.cssSelector(".item"), 5));
wait.until(ExpectedConditions.numberOfElementsToBeMoreThan(By.cssSelector(".row"), 0));
wait.until(ExpectedConditions.numberOfElementsToBeLessThan(By.cssSelector(".error"), 1));
Fluent Wait — Full Control
Fluent wait gives you complete control over polling interval, timeout, and which exceptions to ignore:
import org.openqa.selenium.support.ui.FluentWait;
import java.time.Duration;
import java.util.NoSuchElementException;
FluentWait<WebDriver> fluentWait = new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(30)) // max wait time
.pollingEvery(Duration.ofMillis(500)) // check every 500ms
.ignoring(NoSuchElementException.class) // ignore until timeout
.ignoring(StaleElementReferenceException.class); // also ignore stale refs
// Use with lambda for custom conditions
WebElement element = fluentWait.until(driver -> {
WebElement el = driver.findElement(By.id("dynamicData"));
return el.getText().length() > 0 ? el : null;
// Return null to keep waiting, return non-null to stop
});
// More complex custom condition
String result = fluentWait.until(driver -> {
WebElement counter = driver.findElement(By.id("loadCount"));
String text = counter.getText();
return text.equals("100") ? text : null;
});
Custom Wait Utility
Create a reusable wait utility class:
// src/test/java/com/yourname/selenium/utils/WaitUtils.java
package com.yourname.selenium.utils;
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public class WaitUtils {
private static final int DEFAULT_TIMEOUT = 15;
private static final int SHORT_TIMEOUT = 5;
private static final int LONG_TIMEOUT = 30;
private final WebDriver driver;
private final WebDriverWait wait;
private final WebDriverWait shortWait;
private final WebDriverWait longWait;
public WaitUtils(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, DEFAULT_TIMEOUT);
this.shortWait = new WebDriverWait(driver, SHORT_TIMEOUT);
this.longWait = new WebDriverWait(driver, LONG_TIMEOUT);
}
public WebElement waitForVisible(By locator) {
return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
}
public WebElement waitForClickable(By locator) {
return wait.until(ExpectedConditions.elementToBeClickable(locator));
}
public void waitForInvisible(By locator) {
wait.until(ExpectedConditions.invisibilityOfElementLocated(locator));
}
public void waitForUrl(String urlFragment) {
wait.until(ExpectedConditions.urlContains(urlFragment));
}
public void waitForText(By locator, String text) {
wait.until(ExpectedConditions.textToBePresentInElementLocated(locator, text));
}
public void waitForPageLoad() {
longWait.until(driver ->
((JavascriptExecutor) driver)
.executeScript("return document.readyState")
.equals("complete")
);
}
public void waitForAjax() {
longWait.until(driver ->
(Boolean) ((JavascriptExecutor) driver)
.executeScript("return jQuery.active == 0")
);
}
public boolean isElementPresent(By locator) {
try {
shortWait.until(ExpectedConditions.presenceOfElementLocated(locator));
return true;
} catch (TimeoutException e) {
return false;
}
}
public void clickWhenReady(By locator) {
waitForClickable(locator).click();
}
public void waitForLoadingSpinner(By spinnerLocator) {
// First wait for spinner to appear (it might not appear immediately)
try {
shortWait.until(ExpectedConditions.visibilityOfElementLocated(spinnerLocator));
} catch (TimeoutException ignored) {
// Spinner appeared and disappeared too fast — that's fine
}
// Wait for spinner to disappear
longWait.until(ExpectedConditions.invisibilityOfElementLocated(spinnerLocator));
}
}
Use in tests:
public class LoginTest extends BaseTest {
private WaitUtils waitUtils;
@BeforeMethod
@Override
public void setUp() {
super.setUp();
waitUtils = new WaitUtils(getDriver());
}
@Test
public void testLoginWithWaits() {
WebDriver driver = getDriver();
driver.get("https://app.example.com/login");
// Type credentials
waitUtils.waitForVisible(By.id("email")).sendKeys("user@example.com");
driver.findElement(By.id("password")).sendKeys("password123");
waitUtils.clickWhenReady(By.id("loginBtn"));
// Wait for loading spinner to disappear
waitUtils.waitForLoadingSpinner(By.cssSelector(".spinner"));
// Wait for redirect
waitUtils.waitForUrl("/dashboard");
// Verify dashboard element
WebElement greeting = waitUtils.waitForVisible(By.cssSelector(".user-greeting"));
Assert.assertTrue(greeting.getText().contains("Welcome"));
}
}
Wait Strategy Cheat Sheet
| Situation | Use |
|---|---|
| Simple pages, consistent timing | Implicit wait (set once) |
| AJAX content loading | Explicit wait + visibilityOfElementLocated |
| Button enabling after validation | Explicit wait + elementToBeClickable |
| Loading spinner | Explicit wait + invisibilityOfElementLocated |
| Page redirect | Explicit wait + urlContains |
| Custom conditions | Fluent wait with lambda |
| Never use | Thread.sleep() |
What's Next
In Part 7 we integrate TestNG fully — annotations, test groups, listeners, parallel execution configuration, and building a complete test suite runner with HTML reports.
Discussion
Loading...Leave a Comment
All comments are reviewed before appearing. No links please.