Wednesday, November 30, 2011

Another robust way of how to locate ajax elements using Selenium 2 + webdriver

Check new series of the articles. Review and user experience on test management systems. Functionality and usability.
-------------------

Once you're going to test somehow complicated web-site you should investigate the way of what to consider as the succeess of your another action?

As it looks to me we have two kinds of success indicators of our action on UI
1. You boserve the element that is expected to be displayed (ex.: you're clicking the button and expect to see the dialog)
2. You do not observe the element that is not expected to be displayed. (ex.: you're closing the dialog and expect it disappears)

Also you should take into account that the elements on your page may appear and disappear without page re-loading. That's why I'd like to suggest the following design addressing such the points:

UPD: Here is the sophisticated and of-good-practice way of how to to that.

Introduce two methods:

public WebElement lookupXPathExists(String xpath) throws MyAutomationException{
  WebElement handledElement = repeatableLookupExists(xpath);
  if (handledElement!=null) {
   return handledElement;
  } else {
   throw new MyAutomationException("Lookup for element ["
     + xpath + "]" + " failed after "
     + AWAITING_THRESHOLD_MS + " ms awaiting.");
   
  }
 }
 
 public boolean lookupXPathDoesNotExist(String xpath) throws MyAutomationException {
  if (repeatableLookupDoesNotExist(xpath)) {
   return true;
  }else{
   throw new MyAutomationException("The xpath ["+xpath+"] is still observed after "+AWAITING_THRESHOLD_MS + " ms awaiting.");
  }
 }

Where AWAITING_THRESHOLD_MS is static final variable holding the period you'd like to wait until the element will appear. The first method returns the element found using your locator. The second one just checks if the element you're searching does not present on the page. Both of them call their repeatable helpers which are shown below:

private void validateFoundElements(List<webelement> elementList) throws SeleniumException{
  
  MyTestCase.logger.debug(testCase.wrapMessage("Validating found elements..."));
  
  if(elementList.isEmpty()){
   throw new SeleniumException("Cant find specified element..");
  }
  
  if(countVisibleItems(elementList) > 1){
   throw new SeleniumException("Specified xpath found several elements. Please concretize.");
  }
 }
 
 private int countVisibleItems(List<webelement> elementList){
  int visibleItemsNumber = 0;
  for(WebElement element: elementList){
   if(element.isDisplayed()){
    visibleItemsNumber++;
   }
  }
  return visibleItemsNumber;
 }
 
 private WebElement repeatableLookupExists(String xpath) {
  long start = System.currentTimeMillis();
  while (true) {
   try{
    List<webelement> listOfFoundElements = driver.findElements(By.xpath(xpath));
    validateFoundElements(listOfFoundElements);
    if(driver.findElement(By.xpath(xpath)).isDisplayed()){
     MyTestCase.logger.debug(testCase.wrapMessage("Found xPath [" + xpath + "]. Creating object.."));
     return driver.findElement(By.xpath(xpath));
    }
   }catch(SeleniumException se){
    try {
     Thread.sleep(AWAITING_UNIT_LENGTH_MS);
     MyTestCase.logger.debug(testCase.wrapMessage("Searching xPath [" + xpath + "]. Retry.."));
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
   if (System.currentTimeMillis()-start > AWAITING_THRESHOLD_MS){
    break;
   }
  }
  Statistics.totalLatency += System.currentTimeMillis() - start;
  return null;
 }

 private boolean repeatableLookupDoesNotExist(String xpath) {
  long start = System.currentTimeMillis();
  while (true) {
   boolean isPresent = driver.findElements(By.xpath(xpath)).isEmpty() ? false : true;
   if (!isPresent) {
    Statistics.totalLatency += System.currentTimeMillis() - start;
    MyTestCase.logger.debug(testCase.wrapMessage("Not found xPath [" + xpath + "]. Success.."));
    return true;
   }
   else{
    if(countVisibleItems(driver.findElements(By.xpath(xpath)))==0){
     MyTestCase.logger.debug(testCase.wrapMessage("Found xPath [" + xpath + "]. However it is not visible. Success.."));
     return true;
    }
    try {
     Thread.sleep(AWAITING_UNIT_LENGTH_MS);
     MyTestCase.logger.debug(testCase.wrapMessage("XPath [" + xpath + "] still can be found. Retry.."));
     if (System.currentTimeMillis()-start > AWAITING_THRESHOLD_MS){
      break;
     }
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
  }
  Statistics.totalLatency += System.currentTimeMillis() - start;
  return false;
 }

Where AWAITING_UNIT_LENGTH_MS is final variable saying how often you'd like to check if the element appeared/disappeared.
That is how the solution work. The only thing you should always remember about is that once you want to check if the element is not present on the page and you realize that it isn't you should make sure it is not visible on the page because of the proper working of the tested product but not because of the page hasn't been completely loaded. To make that sure try to determine the checking element that should be obligatory visible while that one you do not expect to see isn't. So use the following pattern to ensure the element is not visible as expected:
1. lookupXPathExists("xpath of the element saying the page is loaded")
2. lookupXPathDoesNotExist("xpath you're expecting not to see on the page").

That is it.