4.14.2013

Selenium Explicit Wait

Explicit wait is a programmatic approach to problem of waiting for elements. Contrary to Implicit Wait that I have described in previous article Explicit Wait requires more coding but also gives much more flexibility. Any action that can be performed using WebDriver can be wrapped using explicit wait and performed over defined period of time checking custom condition. As fast as the condition is satisfied test will continue and if condition was not satisfied for period longer then defined threshold then TimeoutException or any of the not ignored exceptions will be thrown. I will try to address problems that I have described in article about Implicit Wait and show how they can be solved using explicit approach.

1. Performance

What we would like is to have default timeout that will be used in most cases but also possibility to change it if necessary. Fortunately this is what FluentWait class offers. Moreover we can define how frequently we should check the condition. Knowing that we can solve problem of verifying that given element is not present on the page implementing our own condition with custom timeout or just use already provided conditions that are defined in utility class called ExcepctedConditions.

2. Exceptions

Second interesting feature that we get out of the box by using FluentWait is ability to ignore specific exceptions and provide custom message that will be included in exception. No longer we have to start analyzing our tests result from very detailed information about internals of WebDriver or XPath. This is especially convenient when you are dealing with large number of tests. Basically what we are doing is putting another layer of user friendly information that is easier to analyze.

3. Reliability

This is where explicit approach really shines and probably most important reason to use it, especially when testing pages with lot of JavaScript. For example if we want to click on element that is present in DOM but temporarily invisible we can create a condition that will check both presence and visibility. If the element is moving we can try to ignore WebDriverException knowing that we would end up with 'element is not clickable' (this is a bit more risky because WebDriverException can be thrown for other reasons). Finally if we are dealing with StaleElementReferenceException we can try to ignore it and perform whole sequence of actions inside condition body. This means we will periodically try to find element and perform action on it instead of returning if from method and using it later (when it is more likely to end up with stale reference).

4. Conditions

Explicit wait approach is not limited to only findElement and findElements methods of WebDriver. We can use it to chain actions that would normally require few steps and verifications along the way. For example we can define a custom condition that will find input field, type some text and click ok button. Moreover we can easily implement how to conditionally find element. For example wait for progress bar to disappear and then look for element. We can even ask a web developer to set some variable to true if page was loaded or long running task had completed. Then we would use JavascriptExecutor or WebDriver to find that variable and be sure that page is fully rendered and ready to use.

As you can see explicit approach requires more coding but also solves many annoying problems you may run into using implicit wait. Below is a test class that visualize some of the issues I have written about. Please just keep in mind that I am not wrapping invocations of fluent wait because I want to focus on how it can be used.

import com.google.common.base.Function;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.util.concurrent.TimeUnit;
import static org.openqa.selenium.support.ui.ExpectedConditions.*;

public final class ExplicitWaitTest {
    private static final int EXPLICIT_WAIT_POOLING = 500;
    private static final int EXPLICIT_WAIT_TIMEOUT = 7;
    private static final int EXPLICIT_WAIT_CUSTOM_TIMEOUT = 3;
    private static final int EXPLICIT_WAIT_PRESENT_TIMEOUT = 1;
    private WebDriver driver;

    @BeforeTest
    public void initialize() {
        driver = new ChromeDriver(); // requires system property: -Dwebdriver.chrome.driver=<path to chromedriver.exe>
        driver.get("http://docs.seleniumhq.org/");
    }

    @Test(groups = {"performance"}, timeOut = EXPLICIT_WAIT_TIMEOUT * 1000)
    public void customTimeoutTest() {
        // withTimeout - setting custom timeout
        new FluentWait<WebDriver>(driver)
                .pollingEvery(EXPLICIT_WAIT_POOLING, TimeUnit.MILLISECONDS)
                .withTimeout(EXPLICIT_WAIT_CUSTOM_TIMEOUT, TimeUnit.SECONDS)
                .ignoring(NoSuchElementException.class)
                .until(new Function<WebDriver, WebElement>() {
                    @Override
                    public WebElement apply(WebDriver webDriver) {
                        return webDriver.findElement(By.xpath("//div[@id='sidebar']"));
                    }
                });
        // presenceOfElementLocated - defined in ExpectedConditions
        new WebDriverWait(driver, EXPLICIT_WAIT_CUSTOM_TIMEOUT)
                .until(presenceOfElementLocated(By.xpath("//div[@id='sidebar']")));
    }

    @Test(groups = {"performance"}, expectedExceptions = {TimeoutException.class})
    public void verifyTest() {
        // withTimeout - using shorter timeout for checking for non existing elements
        new FluentWait<WebDriver>(driver)
                .pollingEvery(EXPLICIT_WAIT_POOLING, TimeUnit.MILLISECONDS)
                .withTimeout(EXPLICIT_WAIT_PRESENT_TIMEOUT, TimeUnit.SECONDS)
                .ignoring(NoSuchElementException.class)
                .until(new Function<WebDriver, WebElement>() {
                    @Override
                    public WebElement apply(WebDriver webDriver) {
                        return webDriver.findElement(By.xpath("//div[@id='popup']"));
                    }
                });

        // not - defined in ExpectedConditions
        new WebDriverWait(driver, EXPLICIT_WAIT_PRESENT_TIMEOUT)
                .until(not(presenceOfElementLocated(By.xpath("//div[@id='popup']"))));
    }

    @Test(groups = {"exceptions"}, expectedExceptions = {TimeoutException.class})
    public void exceptionHandlingTest() {
        // withMessage - customMessage
        // ignoring - exception we don't care about
        new FluentWait<WebDriver>(driver)
                .pollingEvery(EXPLICIT_WAIT_POOLING, TimeUnit.MILLISECONDS)
                .withTimeout(EXPLICIT_WAIT_PRESENT_TIMEOUT, TimeUnit.SECONDS)
                .ignoring(NoSuchElementException.class)
                .withMessage("Popup is not visible")
                .until(new Function<WebDriver, WebElement>() {
                    @Override
                    public WebElement apply(WebDriver webDriver) {
                        return webDriver.findElement(By.xpath("//div[@id='popup']"));
                    }
                });
    }

    @Test(groups = {"reliability"})
    public void visibilityTest() {
        // ignoring ElementNotVisibleException that will be thrown if we click() on invisible element
        new FluentWait<WebDriver>(driver)
                .pollingEvery(EXPLICIT_WAIT_POOLING, TimeUnit.MILLISECONDS)
                .withTimeout(EXPLICIT_WAIT_TIMEOUT, TimeUnit.SECONDS)
                .ignoring(NoSuchElementException.class, ElementNotVisibleException.class)
                .until(new Function<WebDriver, WebElement>() {
                    @Override
                    public WebElement apply(WebDriver webDriver) {
                        WebElement element = webDriver.findElement(By.xpath("//div[@id='sidebar']"));
                        element.click();
                        return element;
                    }
                });
        // visibilityOfElementLocated - defined in ExpectedConditions
        new WebDriverWait(driver, EXPLICIT_WAIT_TIMEOUT)
                .until(visibilityOfElementLocated(By.xpath("//div[@id='sidebar']")))
                .click();
    }

    @Test(groups = {"reliability"})
    public void movingTest() {
        // ignoring WebDriverException that will be thrown if we click() on moving element
        new FluentWait<WebDriver>(driver)
                .pollingEvery(EXPLICIT_WAIT_POOLING, TimeUnit.MILLISECONDS)
                .withTimeout(EXPLICIT_WAIT_TIMEOUT, TimeUnit.SECONDS)
                .ignoring(NoSuchElementException.class)
                .ignoring(ElementNotVisibleException.class)
                .ignoring(WebDriverException.class)
                .until(new Function<WebDriver, WebElement>() {
                    @Override
                    public WebElement apply(WebDriver webDriver) {
                        WebElement element = webDriver.findElement(By.xpath("//div[@id='sidebar']"));
                        element.click();
                        return element;
                    }
                });
    }

    @Test(groups = {"conditions"})
    public void conditionTest() {
        // finding if there is a progressbar and if not searching for another element
        new FluentWait<WebDriver>(driver)
                .pollingEvery(EXPLICIT_WAIT_POOLING, TimeUnit.MILLISECONDS)
                .withTimeout(EXPLICIT_WAIT_TIMEOUT, TimeUnit.SECONDS)
                .ignoring(NoSuchElementException.class)
                .until(new Function<WebDriver, WebElement>() {
                    @Override
                    public WebElement apply(WebDriver webDriver) {
                        if(webDriver.findElements(By.xpath("//div[@id='progress']")).isEmpty()) {
                            return webDriver.findElement(By.xpath("//div[@id='sidebar']"));
                        }
                        return null;
                    }
                });
    }

    @AfterTest
    public void shutdown() {
        if (driver != null) {
            try {
                driver.quit();
                driver.close();
            } catch (Throwable e) {
                // ignore
            }
        }
    }
}

7 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Great Explanation!! You real time code is very much help for better understand. I would like to visit your blog often. Thanks for your effort. If your audience is also interested in Selenium Testing, they can take a look here: Selenium Training

    ReplyDelete
  3. Wow What A Nice And Great Article, Thank You So Much for Giving Us Such a Nice & Helpful Information about Java, keep sending us such informative articles I visit your website on a regular basis.Please refer below if you are looking for best Training Center.
    Java training in chennai | Java training in annanagar | Java training in omr | Java training in porur | Java training in tambaram | Java training in velachery

    ReplyDelete
  4. Thanks mate. I am really impressed with your writing talents and also with the layout on your weblog. Appreciate, Is this a paid subject matter or did you customize it yourself? Either way keep up the nice quality writing, it is rare to peer a nice weblog like this one nowadays. Thank you, check also event marketing and online registration platforms

    ReplyDelete