Thursday, October 18, 2012

explicit wait interface (200 mcg)

In my opinion, Selenium WebDriver should have a callback mechanism similar to AJAX callback and invoke the callback when HTTP request is settled. This would greatly reduce the waiting period in error cases. When expecting something to appear, there is no indicator to tell whether the element hasn't appear yet, or it will never appear. For now, according to Selenium document, there are two kind of waiting mechanish available to use,

1. Implicit Waits An implicit wait is to tell WebDriver to poll the DOM for a certain amount of time when trying to find an element or elements if they are not immediately available. The default setting is 0. Once set, the implicit wait is set for the life of the WebDriver object instance.
WebDriver driver = new FirefoxDriver();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.get("http://somedomain/url_that_delays_loading");
WebElement myDynamicElement = driver.findElement(By.id("myDynamicElement"));


The disadvantage is after setting implicit wait, WebDriver will wait for all elements which are not found at once which will dramatically slow down the tests in the error condition, so Selenium provided an alternative, Explicit Waits,

2. Explicit Waits An explicit waits is code you define to wait for a certain condition to occur before proceeding further in the code. The worst case of this is Thread.sleep(), which sets the condition to an exact time period to wait. There are some convenience methods provided that help you write code that will wait only as long as required. WebDriverWait in combination with ExpectedCondition is one way this can be accomplished.

WebDriver driver = new FirefoxDriver();
driver.get("http://somedomain/url_that_delays_loading");
WebElement myDynamicElement = (new WebDriverWait(driver, 10))
  .until(ExpectedConditions.presenceOfElementLocated(By.id("myDynamicElement")));
3. FluentWait and WebDriverWait This WebDriverWait, extends FluentWait which implements an interface Wait, and give a timeout as the maximum patience.

At first sight, FluentWait has more complex API then WebDriverWait, WebDriverWait is a refined memeber since it is easier to use and meet your testing need thus I didn't spend time to study FluentWait. I only use WebDriverWait and I found out the places FluentWait used can be replaced by WebDriverWait to simplify the source code.

For example, I found an example using FluentWait here



    FluentWait<WebDriver> wait = new FluentWait<WebDriver>(driver)
            .withTimeout(timeoutSeconds, TimeUnit.SECONDS)
            .pollingEvery(500, TimeUnit.MILLISECONDS)
            .ignoring(NoSuchElementException.class);

    WebElement element = wait.until(new Function<WebDriver, WebElement>() {
        public WebElement apply(WebDriver webDriver) {
            return driver.findElement(locator);
        }
}

can be simplified as this, less code to read for other developers,

    WebDriverWait wait = new WebDriverWait(driver, timeoutSeconds)

    WebElement element = wait.until(new Function<WebDriver, WebElement>() {
        public WebElement apply(WebDriver webDriver) {
            return driver.findElement(locator);
        }

}


As you can see, if you add Explicit Wait to the code, the codebase will be cluttered with this waiting mechanism repeated over and over again.

To avoid repeating this, an interface ExplicitWait is introduced, so it can be selectively called. In the the Searchable interface, a method untilFound is designed to call this until method in the ExplicitWait interface.

public interface ExplicitWait<Where> {

    void save();

    default public <What> What until(Locator<Where, What> predicate) {
        return until(30, SECONDS, predicate);
    }

    default public void until(Predicate<Where> predicate) {
        until(30, SECONDS, predicate);
    }

    default public <What> What until(int duration, TimeUnit timeUnit, Locator<Where, What> locator) {
        try {
            return getFluentWait(duration, timeUnit).until((Where where) -> locator.apply(where));
        } catch (RuntimeException e) {
            save();
            throw e;
        }
    }

    default public void until(int duration, TimeUnit timeUnit, Predicate<Where> predicate) {
        try {
            getFluentWait(duration, timeUnit).until((Where where) -> predicate.test(where));
        } catch (RuntimeException e) {
            save();
            throw e;
        }
    }

    default public FluentWait<Where> getFluentWait(int duration, TimeUnit timeUnit) {
        return new FluentWait<>((Where) this).withTimeout(duration, timeUnit).pollingEvery(50, MILLISECONDS).ignoring(Exception.class);
    }
}


If you want to have a more generic waiting interface like the ExplicitWait in this example, FluentWait is a better candidate to use since it allows you to give a parameterized type as shown. In the development of Selenium Capsules, the reason FluentWait is chosen over WebDriverWait is that there are 3 classes in the framework which wrapper the original WebDriver, WebElement from Selenium. They are AbstractPage, AbstractForm and Element which implement Waitable interface.

In the code, <Where> is a type parameter, it can be an AbstractPage, an Element, or an AbstractForm. Not only T, E can be used as generic type parameter, Where, What or other meaningful words, as long as not conflicting with the Class names used in the containing class, can be used as type parameters.

This interface then can be used everywhere in the project, and have AbstractPage implement it. After any clicking event which triggers a page refresh, call the one of the until methods, either pass in a Predicate or a Locator.

public abstract class AbstractPage implements ExplicitWait<AbstractPage> {

    @Override
    public final Element untilFound(By by) {
        return until(15, SECONDS, (AbstractPage page) -> browser.findElement(by));
    }


This untilFound greatly reduced the duplicate of the testing code. Without this method, each individual developer needs to add a wait statement after each findElement method call. The untilFound method abstracts the complexity away so the page class is cleaner.

/**
 *
 * Copyright (c) 2012, Algocrafts, Inc. All rights reserved.
 * Apache License version 2.
 */
package com.algocrafts.page;
 
import com.algocrafts.selenium.core.WaitWithPatience;
import com.google.common.base.Predicate;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class YujunsBlogPage extends AbstractPage {
 
   public void clickDesignOfDatePickerLink() {
      untilFound(By.linkText("Design of DatePicker").click();
   }
 
   public void clickDesignOfPageLink() {
      untilFound(By.linkText("Design of Page").click();
   }
}

One particular method, untilFound, will be waiting for the element to appear or throwing NoSuchElementException after timed out.

    @Override
    public final Element findElement(By by) {
        try {
            return browser.findElement(by);
        } catch (Exception e) {
        }
        return null;
    }


    /**
     * Find the first element or throw NoSuchElementException
     *
     * @param by selector
     * @return the first element or throw NoSuchElementException
     */
    @Override
    public final Element untilFound(final By by) {
        return until((AbstractPage page) -> browser.findElement(by));
    }

    @Override
    public final Stream<Element> findElements(Supplier<By> by) {
        return browser.findElements(by);
    }

So if you are definitely expecting an element on the page, please use untilFound method. findElement will not wait. The untilFound method is more suitable in a AJAX environment where timing is very important.

ElementLocator calls untilFound while ElementTryLocator calls findElement,

import com.algocrafts.pages.Element;
import com.algocrafts.pages.Searchable;
import org.openqa.selenium.By;

import java.util.function.Supplier;

public class ElementLocator<Where extends Searchable<Where>> extends AbstractLocator<Where, Element> {

    public ElementLocator(Supplier<By> method) {
        super(method);
    }

    @Override
    public Element find(Where where) {
        return where.untilFound(by);
    }
}


import com.algocrafts.pages.Element;
import com.algocrafts.pages.Searchable;
import org.openqa.selenium.By;

import java.util.function.Supplier;

public class ElementTryLocator<Where extends Searchable<Where>> extends AbstractLocator<Where, Element> {

    public ElementTryLocator(Supplier<By> method) {
        super(method);
    }

    @Override
    public Element find(Where where) {
        return where.findElement(by);
    }
}

The ExplicitWait interface is very useful in providing Explicit wait By wrapping FluentWait, since Java 8, it is legal to provide default method implementation inside interface which made the code even cleaner.

Before Java 8, Google Guava code provided two interfaces, Function and Predicate, to enable functional programming in Java. Selenium FluentWait, uses both Function and Predicate from Guava code.

Should Selenium Capsules sticks to Google Guava, or the Function and Predicate from Java 8? The decision I made is to use Java 8. The reason is simple, Java 8 Collection API has a new addition, Stream, which requires the interface of Java 8 Function and Predicate, so using them is beneficial in utilizing new features from Java 8. Then how to convert the Java 8 into Guava Function and Predicate? Actually that's pretty easy in Java 8, let me show all the possible solutions,

1. Plain Old Anonymous Inner Class

    /**
     * @param duration  timeout duration
     * @param timeUnit  unit
     * @param predicate predicate
     * @throws TimeoutException timeout
     */
    default public void until(int duration, TimeUnit timeUnit, final Predicate<Where> predicate) throws TimeoutException {
        try {
            getFluentWait(duration, timeUnit).until(new com.google.common.base.Predicate<Where>() {
                @Override
                public boolean apply(Where where) {
                    return predicate.test(where);
                }
            });
        } catch (TimeoutException e) {
            save();
            throw e;
        }
    }

2. Lambda Expression

    /**
     * @param duration  timeout duration
     * @param timeUnit  unit
     * @param predicate predicate
     * @throws TimeoutException timeout
     */
    default public void until(int duration, TimeUnit timeUnit, Predicate<Where> predicate) throws TimeoutException {
        try {
            getFluentWait(duration, timeUnit).until((Where where) -> predicate.test(where));
        } catch (TimeoutException e) {
            save();
            throw e;
        }
    }


3. Method Reference

    /**
     * @param duration  timeout duration
     * @param timeUnit  unit
     * @param predicate predicate
     * @throws TimeoutException timeout
     */
    default public void until(int duration, TimeUnit timeUnit, Predicate<Where> predicate) throws TimeoutException {
        try {
            getFluentWait(duration, timeUnit).until(predicate::test);
        } catch (TimeoutException e) {
            save();
            throw e;
        }
    }


You can see, Method Reference is the simplest, Java 8 is a huge step forward.

2 comments:

  1. Instead of waiting for a fix amount of time. I have a waitForJSCondition that basically work similar to the waitForCondition on Selenium 1.

    ReplyDelete
  2. @hwang,

    Thank you for your message. It is not waiting for a minute, it is waiting for a condition with one minute timeout. The reason I call it WaitAMinute is that it is cooler than WaitForAConditionWithOneMinuteTimeout.

    ReplyDelete