Use custom conditions with FluentWait in Selenium WebDriver Java automated tests

FluentWait is normally used for waiting for the events generated within the web browser automated testing. However the design of this component implies that one can wait for any sort of event that is possible to capture in Java. In this article we’re going to look at couple of examples of how we can implement waiting for certain conditions which you cannot wait for using existing #ExpectedConditions implementation.

Here in the article we’re not going to learn how FluentWait works in details. Rather we’re going to concentrate on three examples listed below. If you are interested in the topic you can visit my post about FluentWait and ExpectedConditions in Selenium Java. The latter is probably a good place to start from if you do not have much experience with Selenium waiters.

Here is what we’re going to cover in our examples:

  • Wait for file to appear

  • Wait for certain number of elements on a page

  • Wait for an entry in browser console log

How do I wait for a file appearance with FluentWait in Selenium Java

Below is the example showing how to wait for a file that is checked each second with the timeout of 30 seconds.

@Test
@DisplayName("Wait For File Appearance Test")
public void testFileExistence() {
    File fileToWait = new File("///PATH_TO_A_FILE/REQUIRED_FILE_NAME");
    Wait<File> fileWaiter = new FluentWait<>(fileToWait)
            .ignoring(IOException.class)
            .pollingEvery(Duration.ofSeconds(1))
            .withTimeout(Duration.ofSeconds(30));
    fileWaiter.until(file -> file.exists());
}

The code looks even more sophisticated than if you would have such condition in #ExpectedConditions class. Here when we instantiate our waiter we set a File object as FluentWait constructor parameter thus at the same time declaring that until method is going to work with a Function that in turn takes and object of File type as parameter.

Since Function is a functional interface we can implement its only method as lambda expression what is demonstrated in the example. Basically the above code means that the file we have propagated to FluentWait constructor would be polled for existence unless the timeout expires.

How do I wait for certain number of elements with FluentWait in Selenium Java

Sometimes we need to take decision on if the table has completed populating itself with the data. One of the possible criteria is to wait for the certain amount of rows loaded on the UI.

Obviously it is not the only case when we could need to wait for number of rows. Another one that comes to my mind is to wait for certain amount of rows in the table when you test filtering functionality. There are also a lot of different cases when we could use that.

This is going to be a bit more complicated than the previous example with a file. We will need to introduce new class that implements Function interface. Here it is:

class ElementCapacityCondition implements Function<By, Boolean> {

    SearchContext searchContext;
    int expectedCapacity;

    public ElementCapacityCondition(SearchContext searchContext, int expectedCapacity) {
        this.searchContext = searchContext;
        this.expectedCapacity = expectedCapacity;
    }

    @Override
    public Boolean apply(By by) {
        return searchContext.findElements(by).size() == expectedCapacity;
    }
}

In the above code we define our condition as a Function that takes By as a parameter and returns Boolean as the result. What this function is doing is defined in its apply(..) method - it tries to look up elements in specified search context (it could be either a WebElement or WebDriver) and finally checks if the number of detected elements corresponds to what we expect.

Lets now look at how we would use such condition in a test:

@Test
@DisplayName("Wait For Number of Elements")
public void testMultipleElementsInCondition() {

    WebElement table = driver.findElmenet(By.xpath("//table"));
    Wait<By> elementsWaiter = new FluentWait<>(By.tagName("./tr"))
            .withTimeout(Duration.ofSeconds(10))
            .pollingEvery(Duration.ofSeconds(2));
    elementsWaiter.until(new ElementCapacityCondition(table, 10));

}

First we look up for a parent element (just for demonstrating that working with SearchContext is more flexible than with just a WebDriver). In our case it is a table on the page.

Then we create FluentWait with setting our By object in the constructor (hence FluentWait then uses it as parameter to our conditional function when we’ll invoke until method). We also set up some properties of a waiter like polling interval and the timeout.

The last thing left to do is to start our waiter with its until method. As the parameter to that method we set the object of our conditional class for which we also set up search context (a table) and expected number of elements. FluentWait knows that the condition has to have apply method that is to take By as the parameter. Hence it takes By object preserved through its constructor and calls apply method of our condition against it.

How do I wait for a particular browser console log entry with FluentWait in Selenium Java

This is often useful when the error case is not really obvious to catch from UI. Sometimes the events might appear deep inside frontend implementation and devs log them to browser console. Despite such testing is not something normal or usual we still can catch such events from our Selenium scripts.

Disclaimer: working with browser console from Selenium is always a tricky thing. Despite bindings for Java allow fetching logs from browser console, how well such feature works (and even if it works or not at all) depends on the particular WebDriver implementation. For example GeckoDriver does not support this so in my example I’m going to use ChromeDriver. You should consider this aspect when apply the knowledge to your case.

First of all let’s prepare a demo page that would be logging some random messages to the console with certain intervals:

<html>
  <head>
    <script>
      setInterval(
        function(){
          var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
          console.info(characters.charAt(Math.floor(Math.random() * characters.length)).concat(' TEST_MSG'));
        }, 1000
      );
    </script>
  </head>
  <body>
    Testing console message...
  </body>
</html>

You can save this code to HTML file and test manually that the messages really go to the console. Now let’s implement the condition class in the same manner we did in previous example.

class ConsoleMessageCondition implements Function<WebDriver, Boolean> {

    String expectedMessage;

    public ConsoleMessageCondition(String expectedMessage) {
        this.expectedMessage = expectedMessage;
    }

    @Override
    public Boolean apply(WebDriver driver) {
        LogEntries currentEntries = driver.manage().logs().get(LogType.BROWSER);
        System.out.println("Log entries: " + currentEntries.getAll().toString());
        return currentEntries.getAll().stream().anyMatch(logEntry -> logEntry.getMessage().contains(expectedMessage));
    }

}

This condition is built on top of the same principles as previous ones. Unlike the previous condition we define WebDriver as the input parameter for this conditional Function because the logs are accessible from WebDriver object only.

Each time FluentWait will be polling for meeting the conditions our apply method will be fetching all the logs which hasn’t yet been fetched from the console. Having them packed in LogEntries we can apply java streams in order to check if they contain the pattern we are looking for.

Log entry itself does not have any structure however there is a lot of information except of the message you are looking for (timestamps, message level, etc.) so take it into account when you compare the message with some expected result.

So, now we have implemented our condition but we still can’t yet proceed to the test logic describing. Before that we have to configure our WebDriver to let it know we’re going to work with logs in our tests. This is done by the following code:

@BeforeEach
public void setUp() {
    ChromeOptions options = new ChromeOptions();
    LoggingPreferences logPrefs = new LoggingPreferences();
    logPrefs.enable(LogType.BROWSER, Level.ALL);
    options.setCapability("goog:loggingPrefs", logPrefs);
    driver = new ChromeDriver(options);
}

Now we’re done and here is our sample test:

@Test
public void testLogInBrowserConsole() {
    driver.get("file:///path_to_saved_html_page_from_example");
    Wait<WebDriver> logMessageWaiter = new FluentWait<>(driver)
            .pollingEvery(Duration.ofSeconds(1))
            .withTimeout(Duration.ofSeconds(30));
    logMessageWaiter.until(new ConsoleMessageCondition("K TEST_MSG"));
}

Since the page generates messages randomly the above test might pass or fail depending on probability.

If you still have the questions please send them to me using this form. I will amend the article according to your feedback.