In the previous lesson, you read the five-second message on Appearing Elements too early. findElement() worked, isDisplayed() returned false, and getText() returned an empty string. You also saw that a fixed pause is not a fix.

Selenium has three built-in waits for this kind of problem: implicit, explicit, and fluent. They sound similar, but they do different jobs, and picking the wrong one is a common reason a “fixed” test stays flaky. This lesson compares them, shows exactly why one of them cannot fix the Appearing Elements message, and then uses the right one to read that message reliably.

To see the difference, start with the page that caused the problem.

Open Appearing Elements in your browser and watch the first message. Its element has the id appearing5sec. It is already in the page’s HTML when the page loads, but it is hidden. About five seconds later, the page shows it, and it reads Message appeared after 5 seconds.

Your test needs to say: “keep checking until this message is visible, but do not wait forever.” Selenium offers three ways to wait, and only one of them can express that sentence.

Before any code, here is the whole picture. Read the table once now and come back to it at the end of the lesson.

Implicit waitExplicit waitFluent wait
What it checksWhether the element can be found in the pageAny condition you name, such as “visible” or “clickable”Any condition you write, with your own timing rules
Where you set itOnce on the driver, usually in setupRight before the step that needs itRight before the step that needs it
What it applies toEvery findElement() call for the whole sessionOne condition at a timeOne condition at a time
Main Selenium typedriver.manage().timeouts().implicitlyWait(...)WebDriverWaitFluentWait
Control you getOne timeout numberTimeout, plus the default check every 500 msTimeout, polling interval, ignored exceptions, custom message
Use it whenThe element is not in the page yetYou wait for a state: visible, clickable, text changedYou need to change the polling or the ignored errors

Three short definitions will make the table readable:

  • An implicit wait is a single timeout that you set on the driver. After that, whenever findElement() cannot find an element, Selenium keeps looking until the timeout ends before it gives up.
  • An explicit wait is a wait you place in one spot in your test, for one named condition. Selenium checks that condition again and again until it is true or the time limit ends.
  • A fluent wait is an explicit wait where you also control how often Selenium checks and which errors count as “not yet”.

And for contrast, Thread.sleep() is not a Selenium wait at all. It is plain Java that stops your test for a fixed time. It never looks at the page, so it always waits the full time, and it still fails if the page is slower than your guess. It stays in this lesson only as a comparison.

The most important row is the first one. An implicit wait checks one thing: can the element be found. The Appearing Elements message is already found at load. Its problem is that it is not visible yet. That is a different question, and it needs a different wait.

The Selenium documentation describes the implicit wait as a global setting that applies to every element location call for the entire session. Its default value is 0, which means that when an element is not found, Selenium reports the error immediately. If you set a value, the driver waits up to that long before reporting the error. As soon as the element is located, it returns right away, so a larger value does not make every step slower.

Here is the line that sets it:

driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));

Read it from left to right: take the driver, open its timeout settings, and set the implicit wait to ten seconds. Duration.ofSeconds(10) is Java’s way of writing a length of time. You usually put this line in your setup, right after starting the browser, and it then affects every later findElement().

An implicit wait is a good fit when an element is added to the page a moment after you ask for it. It does not help when the element exists but you need it to be visible. To see this, run a small experiment on the real page.

Create this file in your selenium-java-academy project:

src/test/java/com/codekru/tests/ImplicitWaitTest.java

The class sets a ten second implicit wait, then asks for appearing5sec and looks at what comes back:

package com.codekru.tests;

import java.time.Duration;

import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class ImplicitWaitTest {

    private WebDriver driver;

    @BeforeMethod
    public void setUp() {
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        driver.get("https://testkru.com/Interactions/AppearingElements");
    }

    @Test
    public void implicitWaitReturnsTheHiddenMessageImmediately() {
        long start = System.currentTimeMillis();
        WebElement message = driver.findElement(By.id("appearing5sec"));
        long millis = System.currentTimeMillis() - start;

        System.out.println("findElement took " + millis + " ms");
        System.out.println("isDisplayed: " + message.isDisplayed());
        System.out.println("getText: [" + message.getText() + "]");

        Assert.assertFalse(message.isDisplayed());
        Assert.assertEquals(message.getText(), "");
    }

    @Test
    public void implicitWaitKeepsLookingForAMissingElement() {
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));

        long start = System.currentTimeMillis();
        Assert.expectThrows(NoSuchElementException.class,
                () -> driver.findElement(By.id("no-such-element")));
        long seconds = (System.currentTimeMillis() - start) / 1000;

        System.out.println("Gave up after about " + seconds + " seconds");
        Assert.assertTrue(seconds >= 3);
    }

    @AfterMethod(alwaysRun = true)
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

The setup and cleanup methods are the ones you already wrote in earlier lessons. The new line is implicitlyWait(Duration.ofSeconds(10)). The first test then measures three things with a stopwatch built from System.currentTimeMillis(), which returns the current time in milliseconds:

  1. How long findElement() took.
  2. Whether the element it returned is displayed.
  3. What text it returned.

Run the class as a TestNG test. Both tests should pass. The console shows output like this:

findElement took 9 ms
isDisplayed: false
getText: []
Gave up after about 3 seconds

The exact milliseconds will differ on your machine, but the pattern will not. findElement() returned in a few milliseconds, not ten seconds, because the element was already in the HTML and the implicit wait had nothing to wait for. isDisplayed() is still false, and getText() is still empty, exactly like the failure in the previous lesson.

The second test shows what an implicit wait does do. You ask for an id that does not exist anywhere on the page, no-such-element, which is a made-up id used only for this experiment. With a three second implicit wait, Selenium keeps looking for about three seconds and then throws NoSuchElementException. That is the job of an implicit wait: it waits for an element to exist. It never waits for an element to become visible.

So for this page, an implicit wait is the wrong tool. You need a wait that checks visibility.

An explicit wait fixes the problem because you tell Selenium exactly what to wait for. In your test, that sentence is “wait until appearing5sec is visible, for up to ten seconds.”

Two classes do the work.

WebDriverWait is the waiting object. You create it with your driver and a maximum time:

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

This line does not wait yet. It only creates the waiting tool and says, “never wait longer than ten seconds.”

ExpectedConditions is a collection of ready-made conditions. Each one describes something that can become true on a page. The one you need is visibilityOfElementLocated(). It means: find the element with this locator, and report success only when the element is displayed. If the element is missing, or it is there but hidden, the answer is “not yet” and the wait tries again.

You connect the two with until():

WebElement message = wait.until(
        ExpectedConditions.visibilityOfElementLocated(By.id("appearing5sec")));

until() is the moment of waiting. It checks the condition, and if the answer is “not yet”, it pauses briefly and checks again. When the condition becomes true, it returns the element and your code continues. You get back an element that is already visible, so no extra isDisplayed() check is needed.

By default, WebDriverWait checks about every 500 milliseconds. For appearing5sec, that is roughly ten checks that say “not yet”, then one that says “visible”. It also ignores NoSuchElementException while it waits, so an element not yet added to the page does not crash the wait.

The wait ends as soon as the condition is true, so a ten second limit does not mean ten seconds every time. On this page, the test continues after about five seconds. Thread.sleep(10000), by contrast, always pauses the full time.

Add a second class to the same project. Save it as:

src/test/java/com/codekru/tests/ExplicitWaitTest.java

If your project uses a different base package, change the package line to match it.

The next snippets are methods that belong inside the ExplicitWaitTest class, so they are not a complete file on their own. The complete file is under the heading “The complete explicit wait class” below. If you prefer, paste that complete file now instead of assembling the snippets one by one.

The setup is the same as the implicit class, without the implicit wait line. That is deliberate, and the section on mixing waits explains why.

@BeforeMethod
public void setUp() {
    driver = new ChromeDriver();
    driver.manage().window().maximize();
    driver.get("https://testkru.com/Interactions/AppearingElements");
}

@AfterMethod(alwaysRun = true)
public void tearDown() {
    if (driver != null) {
        driver.quit();
    }
}

alwaysRun = true tells TestNG to run the cleanup even when the test fails, so a failing wait never leaves a Chrome window open.

Now the first test, with the wait:

@Test
public void shouldReadMessageAfterItBecomesVisible() {
    Assert.assertEquals(driver.getTitle(), "Appearing Elements");

    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    WebElement message = wait.until(
            ExpectedConditions.visibilityOfElementLocated(By.id("appearing5sec")));

    Assert.assertTrue(message.isDisplayed());
    Assert.assertEquals(message.getText(), "Message appeared after 5 seconds");
}

Read it from top to bottom:

  1. The title check confirms you are on the page you meant to test.
  2. new WebDriverWait(...) creates a wait with a ten second limit.
  3. wait.until(...) holds the test until appearing5sec is visible, then returns it.
  4. isDisplayed() is true by now, and getText() returns the real message.

Compare this with the implicit experiment. There, getText() returned an empty string. Here, the wait guarantees the element is visible first, so the text is there to read. The expected value is the exact text from the live page: Message appeared after 5 seconds.

It is better to see a wait fail on purpose than to meet the failure first in a real project. Make the wait shorter than the page delay. The page needs five seconds, so give the wait only two:

@Test
public void shouldTimeOutWhenTheWaitIsShorterThanThePageDelay() {
    WebDriverWait shortWait = new WebDriverWait(driver, Duration.ofSeconds(2));

    TimeoutException timeout = Assert.expectThrows(TimeoutException.class, () ->
            shortWait.until(ExpectedConditions.visibilityOfElementLocated(By.id("appearing5sec"))));

    System.out.println(timeout.getMessage());
}

Assert.expectThrows() is a TestNG method for a failure you expect. You give it the exception type and a block of code. The () -> part is a Java lambda, a small piece of code passed in to run later. The test passes only if a TimeoutException is thrown, and it hands you the exception so you can print its message.

When this test runs, the wait stops after two seconds, and the printed message starts like this:

Expected condition failed: waiting for visibility of element found by By.id: appearing5sec
(tried for 2 seconds with 500 milliseconds interval)

Selenium prints more lines after these, such as your Selenium version and browser details. The first two lines are the useful ones. They tell you what it was waiting for (visibility of appearing5sec), how long it tried (two seconds), and how often it checked (every 500 milliseconds).

A TimeoutException does not mean Selenium is broken. It means the condition never became true in the time you allowed. When you see one, ask:

  • Is the time limit simply too short for this page?
  • Is the locator correct? A wrong id makes the condition wait for an element that never exists.
  • Is the element present but never shown, perhaps because an earlier step did not happen?
  • Did the page show an error instead of the content you expected?

Here the recovery is to raise the limit to ten seconds. On a real page, find the cause first, because a longer wait for a wrong locator only makes the failure slower.

Now that WebDriverWait works, you can meet FluentWait. It is the more flexible wait that WebDriverWait is built on. With FluentWait, you set three things yourself:

  • the timeout, how long to keep trying
  • the polling interval, how often to check
  • the exceptions to ignore, which errors count as “not yet” instead of a failure

WebDriverWait already picks sensible values for these: a 500 millisecond polling interval and NoSuchElementException ignored. FluentWait lets you change them. Here is the same job. This method is the third test inside the same ExplicitWaitTest class, and it is already included in the complete file below:

@Test
public void shouldReadMessageWithFluentWait() {
    FluentWait<WebDriver> fluentWait = new FluentWait<>(driver)
            .withTimeout(Duration.ofSeconds(10))
            .pollingEvery(Duration.ofMillis(250))
            .ignoring(NoSuchElementException.class);

    WebElement message = fluentWait.until(d -> {
        WebElement candidate = d.findElement(By.id("appearing5sec"));
        return candidate.isDisplayed() ? candidate : null;
    });

    Assert.assertEquals(message.getText(), "Message appeared after 5 seconds");
}

The new pieces are small:

  1. new FluentWait<>(driver) creates the wait around your driver. The <WebDriver> in the type tells Java what the wait works with.
  2. withTimeout() sets the limit, ten seconds here.
  3. pollingEvery() sets the check interval, 250 milliseconds instead of the default 500.
  4. ignoring() names an exception that should be treated as “not yet”. Here it is NoSuchElementException, which findElement() throws if the element is not in the page yet.
  5. The condition inside until() is a lambda you write yourself. The letter d is the driver that the wait passes in. The wait reruns your lambda until it returns something other than null or false. Here it returns the element when visible, and null when it is not, which means “check again”.

On Appearing Elements, this behaves like the first explicit test. You did not need FluentWait here, and that is an honest answer to “which should I use?” Reach for FluentWait when you need control the shorter form does not give you:

  • a slower polling interval for a page that is expensive to check
  • a faster polling interval where timing matters
  • ignoring an exception other than the default one
  • a condition that is not in ExpectedConditions, so you write it yourself

Since WebDriverWait is built on FluentWait, nothing you learned is wasted. It is the same mechanism with the defaults already filled in.

You can now answer the question the table raised. Ask yourself what your test is waiting for:

  • The element may not exist yet. An implicit wait can cover this, but it is a blunt, global setting.
  • The element exists but must be visible, clickable, or changed. Use an explicit wait with an ExpectedCondition. This is the right choice for the Appearing Elements message.
  • You need different polling, ignored errors, or a custom condition. Use FluentWait.
  • You are tempted to type Thread.sleep(). Pick one of the three waits above instead.

For this course, use explicit waits as your default, and leave the implicit wait at its default of 0. The reason is a warning in the official Selenium waits documentation, checked while writing this lesson: do not mix implicit and explicit waits, because doing so can cause unpredictable wait times. Its own example says that an implicit wait of 10 seconds with an explicit wait of 15 seconds could cause a timeout after 20 seconds. Here is a way to picture why. An explicit wait looks the element up again on every check. If an implicit wait is also set, each of those lookups can itself pause for up to the implicit time before the explicit wait moves on to its next check. The two timers add up in ways that are hard to predict, so a failing test becomes slow and puzzling. Choose one style per project.

Here is the finished ExplicitWaitTest.java, with all three tests and the setup together:

package com.codekru.tests;

import java.time.Duration;

import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class ExplicitWaitTest {

    private WebDriver driver;

    @BeforeMethod
    public void setUp() {
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.get("https://testkru.com/Interactions/AppearingElements");
    }

    @Test
    public void shouldReadMessageAfterItBecomesVisible() {
        Assert.assertEquals(driver.getTitle(), "Appearing Elements");

        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        WebElement message = wait.until(
                ExpectedConditions.visibilityOfElementLocated(By.id("appearing5sec")));

        Assert.assertTrue(message.isDisplayed());
        Assert.assertEquals(message.getText(), "Message appeared after 5 seconds");
    }

    @Test
    public void shouldTimeOutWhenTheWaitIsShorterThanThePageDelay() {
        WebDriverWait shortWait = new WebDriverWait(driver, Duration.ofSeconds(2));

        TimeoutException timeout = Assert.expectThrows(TimeoutException.class, () ->
                shortWait.until(ExpectedConditions.visibilityOfElementLocated(By.id("appearing5sec"))));

        System.out.println(timeout.getMessage());
    }

    @Test
    public void shouldReadMessageWithFluentWait() {
        FluentWait<WebDriver> fluentWait = new FluentWait<>(driver)
                .withTimeout(Duration.ofSeconds(10))
                .pollingEvery(Duration.ofMillis(250))
                .ignoring(NoSuchElementException.class);

        WebElement message = fluentWait.until(d -> {
            WebElement candidate = d.findElement(By.id("appearing5sec"));
            return candidate.isDisplayed() ? candidate : null;
        });

        Assert.assertEquals(message.getText(), "Message appeared after 5 seconds");
    }

    @AfterMethod(alwaysRun = true)
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

Your pom.xml from Module 0 already has what both classes need. They were run with Selenium Java 4.46.0 and TestNG 7.12.0, and the explicit wait class also ran on 4.50.0.

  1. Save both classes in the paths shown above.
  2. Right-click each class name in IntelliJ and run it as a TestNG test.
  3. Watch the Chrome windows. Each test opens its own browser and closes it at the end.

What you should observe:

  • ImplicitWaitTest: two tests pass. The first prints a very short findElement time, isDisplayed: false, and an empty text. The second prints Gave up after about 3 seconds.
  • ExplicitWaitTest: three tests pass. The first and third pause about five seconds and then read the message. The second stops after about two seconds, passes because it expected the timeout, and prints the timeout message.
  • No Chrome window is left open.

If you also see a WARNING about a “CDP version” in the console, you can ignore it. It only says that your Chrome browser is newer than the developer tools support built into your Selenium version. The tests still run.

Expecting an implicit wait to wait for visibility. You just saw it return a hidden element in a few milliseconds. If a test reads empty text from an element that appears later, look for a wait on a visibility condition, not a larger implicit timeout.

Mixing an implicit wait with an explicit wait. If your setup sets implicitlyWait(...) and a test also uses WebDriverWait, the waits can combine and produce unpredictable delays. Remove the implicit wait line and rely on explicit waits.

Setting the time limit shorter than the delay. A two second wait on a five second message always times out. Compare the tried for N seconds line with how long the page really needs.

Calling findElement() first and waiting second. This version looks harmless but does the wrong thing:

WebElement message = driver.findElement(By.id("appearing5sec"));
wait.until(ExpectedConditions.visibilityOf(message));

On this page it happens to work, because the element exists from the start. On a page where the element is added later, findElement() would fail before the wait began. Passing a locator to visibilityOfElementLocated() lets the wait do the finding on every check, which is the safer habit.

Use the skills from this lesson on a fresh problem. The starting state is an empty Java method with the driver already set up and no implicit wait.

Open Appearing Elements, wait until the element with the id appearing5sec is visible for up to ten seconds, and print a line that begins with Element visible: followed by the visible text. When you run it, the console should show Element visible: Message appeared after 5 seconds.

A hint if you get stuck: you need one WebDriverWait, one ExpectedConditions condition, and the text of the element that until() returns. Join Element visible: and that text with + when you print. Try to write it without looking back at the complete class.

Continue practicing: Explicit Wait – Element Visible

When it passes, try one extra experiment. Change the wait limit to three seconds, run the test, and read the timeout message. Then change it back and confirm the test passes again.

You can name the three Selenium waits and say what each checks. You can explain why an implicit wait cannot read appearing5sec: the element is already in the HTML, so findElement() returns it at once while it is still hidden. You can wait for a visible element with WebDriverWait and ExpectedConditions, recognize a TimeoutException, and know not to mix implicit and explicit waits.

Your waits so far live inside each test. Lesson 18, Build Reliable Tests: Reusable Waits and Common Synchronization Patterns, takes that step. Until then, practice the explicit wait you have just learned, because everything in lesson 18 builds on it.