Monday, March 17, 2014

Webdriver Waits






An implementation of the Wait interface that may have its timeout and polling interval configured on the fly.
Each FluentWait instance defines the maximum amount of time to wait for a condition, as well as the frequency with which to check the condition. Furthermore, the user may configure the wait to ignore specific types of exceptions whilst waiting, such as NoSuchElementExceptions when searching for an element on the page.

 // Waiting 30 seconds for an element to be present on the page, checking
   // for its presence once every 5 seconds.
   Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
       .withTimeout(30, SECONDS)
       .pollingEvery(5, SECONDS)
       .ignoring(NoSuchElementException.class);

   WebElement foo = wait.until(new Function<WebDriver, WebElement>() {
     public WebElement apply(WebDriver driver) {
       return driver.findElement(By.id("foo"));
     }
   });
 


Implicit wait

 WebDriver driver = new FirefoxDriver();  
 driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);  

Implicit waits are basically your way of telling WebDriver the latency that you want to see if specified web element is not present that WebDriver looking for. So in this case, you are telling WebDriver that it should wait 10 seconds in cases of specified element not available on the UI (DOM). 

  • Implicit wait time is applied to all elements in your script

Explicit wait (By Using FluentWait)

 
public static WebElement explicitWait(WebDriver driver,By by)  
{  
    Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)  
             .withTimeout(20, TimeUnit.SECONDS)  
             .pollingEvery(2, TimeUnit.SECONDS)  
             .ignoring(NoSuchElementException.class); 

     WebElement element= wait.until(new Function<WebDriver, WebElement>() {  
           public WebElement apply(WebDriver driver) {  
             return driver.findElement(By.id("foo"));  
            }  
      });  
  return element;  
 }  

Explicit waits are intelligent waits that are confined to a particular web element. Using explicit waits you are basically telling  WebDriver at the max it is to wait for X units of time before it gives up.

  • Explicit wait time is applied only for particular specified element.
  •  In Explicit you can configure, how frequently (instead of 2 seconds) you want to check condition 
Explicit wait

public static WebElement explicitWait(WebDriver driver,By by) { WebDriverWait wait = new WebDriverWait(driver, 30); wait.until(ExpectedConditions.presenceOfElementLocated(by)); }
In above method it will wait until either ExpectedConditions become true or Timeout (30 sec).

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
            }
        }
    }
}
 

1 comment:

TestNG - Can i use the 2 different data providers to same @test methods in TestNG?

public Object [][] dp1 () { return new Object [][] { new Object [] { "a" , "b" }, new Obje...