Lesson 14 of 14 ~18 min
Course progress
0%

Waits and Synchronization in Selenium

Master explicit, implicit, and fluent waits to handle dynamic web applications

Handling Asynchronous Web Applications

Modern web applications load content dynamically, requiring proper synchronization strategies. Selenium provides three types of waits to handle timing issues effectively.

The Problem: Why We Need Waits

// ❌ Bad - Will often fail due to timing
driver.get("https://example.com");
driver.findElement(By.id("dynamic-content")).click();
// Element might not be loaded yet!

Dynamic content, AJAX calls, animations, and lazy loading require waiting for elements to be ready before interaction.

Implicit Wait

Implicit wait tells WebDriver to poll the DOM for a certain duration when trying to find an element.

import java.time.Duration;

public class ImplicitWaitExample {
    public static void main(String[] args) {
        WebDriver driver = new ChromeDriver();
        
        // Set implicit wait - applies to all findElement calls
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        
        driver.get("https://thinkdifferent.blog");
        
        // Will wait up to 10 seconds for element to appear
        driver.findElement(By.cssSelector("[data-testid='post-card']")).click();
        
        driver.quit();
    }
}

Pros:

  • Simple to implement
  • Applies globally
  • Good for basic scenarios

Cons:

  • Can slow down tests
  • Not suitable for specific conditions
  • Conflicts with explicit waits

Explicit Wait

Explicit waits provide fine-grained control over wait conditions for specific elements.

import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;

public class ExplicitWaitExample {
    public static void main(String[] args) {
        WebDriver driver = new ChromeDriver();
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        
        driver.get("https://thinkdifferent.blog/courses");
        
        // Wait for specific element to be clickable
        WebElement coursesGrid = wait.until(
            ExpectedConditions.elementToBeClickable(
                By.cssSelector("[data-testid='courses-grid']")
            )
        );
        
        coursesGrid.click();
        driver.quit();
    }
}

Common Expected Conditions

Selenium provides many built-in expected conditions:

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

// 1. Element to be clickable
wait.until(ExpectedConditions.elementToBeClickable(By.id("submit-btn")));

// 2. Visibility of element
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("success-message")));

// 3. Presence of element (in DOM, may not be visible)
wait.until(ExpectedConditions.presenceOfElementLocated(By.id("hidden-field")));

// 4. Text to be present in element
wait.until(ExpectedConditions.textToBePresentInElementLocated(
    By.id("status"), "Completed"
));

// 5. Invisibility of element
wait.until(ExpectedConditions.invisibilityOfElementLocated(By.id("loader")));

// 6. Alert is present
wait.until(ExpectedConditions.alertIsPresent());

// 7. Title contains
wait.until(ExpectedConditions.titleContains("Think Different"));

// 8. URL contains
wait.until(ExpectedConditions.urlContains("/courses"));

// 9. Number of elements
wait.until(ExpectedConditions.numberOfElementsToBeMoreThan(
    By.cssSelector("[data-testid='course-card']"), 5
));

// 10. Staleness of element (element is no longer attached to DOM)
WebElement element = driver.findElement(By.id("refresh-me"));
driver.navigate().refresh();
wait.until(ExpectedConditions.stalenessOf(element));
public class BlogSearchTest {
    private WebDriver driver;
    private WebDriverWait wait;
    
    @BeforeMethod
    public void setup() {
        driver = new ChromeDriver();
        wait = new WebDriverWait(driver, Duration.ofSeconds(15));
        driver.get("https://thinkdifferent.blog/blog");
    }
    
    @Test
    public void testBlogSearch() {
        // Wait for search box to be present
        WebElement searchBox = wait.until(
            ExpectedConditions.presenceOfElementLocated(
                By.cssSelector("input[placeholder*='Search']")
            )
        );
        
        searchBox.sendKeys("Playwright");
        
        // Wait for search results to update
        wait.until(ExpectedConditions.textToBePresentInElementLocated(
            By.cssSelector("[data-testid='post-list']"), "Playwright"
        ));
        
        // Verify results
        List<WebElement> posts = driver.findElements(
            By.cssSelector("[data-testid='post-card']")
        );
        
        Assert.assertTrue(posts.size() > 0, "Search results should be visible");
    }
    
    @AfterMethod
    public void tearDown() {
        driver.quit();
    }
}

Fluent Wait

Fluent Wait is the most flexible, allowing custom polling intervals and exception handling.

import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.NoSuchElementException;

public class FluentWaitExample {
    public static void main(String[] args) {
        WebDriver driver = new ChromeDriver();
        
        FluentWait<WebDriver> wait = new FluentWait<>(driver)
            .withTimeout(Duration.ofSeconds(30))
            .pollingEvery(Duration.ofSeconds(2))
            .ignoring(NoSuchElementException.class)
            .withMessage("Element not found after 30 seconds");
        
        driver.get("https://thinkdifferent.blog");
        
        WebElement element = wait.until(driver -> {
            WebElement el = driver.findElement(
                By.cssSelector("[data-testid='post-card']")
            );
            return el.isDisplayed() ? el : null;
        });
        
        element.click();
        driver.quit();
    }
}

Custom Wait Conditions

Create reusable custom wait conditions:

public class CustomConditions {
    
    // Wait for element to have specific CSS class
    public static ExpectedCondition<Boolean> elementHasClass(
        By locator, String className
    ) {
        return driver -> {
            WebElement element = driver.findElement(locator);
            String classes = element.getAttribute("class");
            return classes != null && classes.contains(className);
        };
    }
    
    // Wait for element attribute value
    public static ExpectedCondition<Boolean> attributeContains(
        By locator, String attribute, String value
    ) {
        return driver -> {
            WebElement element = driver.findElement(locator);
            String attrValue = element.getAttribute(attribute);
            return attrValue != null && attrValue.contains(value);
        };
    }
    
    // Wait for AJAX to complete (jQuery)
    public static ExpectedCondition<Boolean> jQueryAjaxCompleted() {
        return driver -> {
            JavascriptExecutor js = (JavascriptExecutor) driver;
            return (Boolean) js.executeScript("return jQuery.active == 0");
        };
    }
}

// Usage
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(CustomConditions.elementHasClass(
    By.id("loading-spinner"), "hidden"
));

Course Navigation Example

@Test
public void navigateToCourseLesson() {
    driver.get("https://thinkdifferent.blog/courses");
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
    
    // 1. Wait for courses to load
    wait.until(ExpectedConditions.presenceOfElementLocated(
        By.cssSelector("[data-testid='courses-grid']")
    ));
    
    // 2. Click on first course
    WebElement firstCourse = wait.until(
        ExpectedConditions.elementToBeClickable(
            By.cssSelector("[data-testid='course-card-link']")
        )
    );
    firstCourse.click();
    
    // 3. Wait for course page to load
    wait.until(ExpectedConditions.urlContains("/courses/"));
    
    // 4. Wait for modules to be visible
    wait.until(ExpectedConditions.visibilityOfElementLocated(
        By.cssSelector("[data-testid='course-modules']")
    ));
    
    // 5. Click first module
    WebElement firstModule = wait.until(
        ExpectedConditions.elementToBeClickable(
            By.cssSelector("[data-testid='course-module-link']")
        )
    );
    firstModule.click();
    
    // 6. Wait for lesson content
    wait.until(ExpectedConditions.presenceOfElementLocated(
        By.cssSelector("[data-testid='page-lesson']")
    ));
    
    Assert.assertTrue(
        driver.getCurrentUrl().contains("/courses/"),
        "Should be on course lesson page"
    );
}

Waiting for Dynamic Content

Handle loading states and dynamic content:

public class DynamicContentTest {
    
    @Test
    public void waitForContentToLoad() {
        driver.get("https://thinkdifferent.blog/blog");
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20));
        
        // 1. Wait for loader to disappear
        wait.until(ExpectedConditions.invisibilityOfElementLocated(
            By.id("loading-spinner")
        ));
        
        // 2. Wait for content to be visible
        wait.until(ExpectedConditions.visibilityOfElementLocated(
            By.cssSelector("[data-testid='post-list']")
        ));
        
        // 3. Wait for specific number of posts
        wait.until(ExpectedConditions.numberOfElementsToBeMoreThan(
            By.cssSelector("[data-testid='post-card']"), 3
        ));
        
        List<WebElement> posts = driver.findElements(
            By.cssSelector("[data-testid='post-card']")
        );
        Assert.assertTrue(posts.size() >= 3, "At least 3 posts should be loaded");
    }
}

Best Practices

  1. Prefer Explicit Waits over Implicit Waits
  2. Use ExpectedConditions for common scenarios
  3. Create Custom Conditions for complex cases
  4. Set Reasonable Timeouts (10-30 seconds typically)
  5. Wait for Specific Conditions not arbitrary sleeps
  6. Handle Stale Elements with staleness check
  7. Chain Waits for multi-step interactions

Anti-Patterns to Avoid

// ❌ Never use Thread.sleep()
Thread.sleep(5000);

// ❌ Don't mix implicit and explicit waits
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); // Confusing!

// ❌ Don't wait for arbitrary time
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30)); // Too long!

// ✅ Use explicit waits with specific conditions
wait.until(ExpectedConditions.elementToBeClickable(element));

Practical Utility Class

public class WaitUtils {
    private WebDriverWait wait;
    
    public WaitUtils(WebDriver driver, int timeoutSeconds) {
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(timeoutSeconds));
    }
    
    public WebElement waitForClickable(By locator) {
        return wait.until(ExpectedConditions.elementToBeClickable(locator));
    }
    
    public WebElement waitForVisible(By locator) {
        return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
    }
    
    public void waitForInvisible(By locator) {
        wait.until(ExpectedConditions.invisibilityOfElementLocated(locator));
    }
    
    public void waitForTextInElement(By locator, String text) {
        wait.until(ExpectedConditions.textToBePresentInElementLocated(locator, text));
    }
    
    public void waitForUrlContains(String urlPart) {
        wait.until(ExpectedConditions.urlContains(urlPart));
    }
}

// Usage
WaitUtils waitUtils = new WaitUtils(driver, 15);
waitUtils.waitForClickable(By.id("submit")).click();
waitUtils.waitForTextInElement(By.id("message"), "Success");

Key Takeaways

✅ Explicit waits provide better control than implicit waits
✅ Use ExpectedConditions for common scenarios
✅ Fluent waits offer maximum flexibility
✅ Create custom conditions for specific needs
✅ Never use Thread.sleep() in tests
✅ Set reasonable timeout values
✅ Wait for specific conditions, not time