Friday, July 08, 2011

Custom way to look-up ajax elements in Selenium. Pattern.

Hi!

Looking through a lot of forums I noticed that some people face problems when use native selenium instructions to wait for element appearance on the page. That become pretty big problem when we're talking about dynamic HTML content. In my practice I met the close problems and the way I resolved them is the following pattern. This class controls the look-up procedures on the page. I designed to be capable to configure the duration and number of repeat lookups depending on the channel bandwidth and other factors. Also it has the method allowing us to wait while the required element disappears. That is pretty much common situation in web testing as well. So the class looks like this:


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


package ar.example;

import java.util.HashMap;
import java.util.Properties;

import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.SeleniumException;

public class SeleniumAdapter {
 
 private final int AWAITING_UNIT_LENGTH_MS = 300;
 private final long AWAITING_THRESHOLD_MS = 10000;
 private Selenium driver;
 

    public static String getRelativePath(String testedDomain, String alteredPath){
   return testedDomain + alteredPath;
 }
 
 public SeleniumAdapter(Properties properties) {
  driver = new DefaultSelenium(properties.getProperty("host"),  Integer.parseInt(properties.getProperty("port")), properties.getProperty("browser"), properties.getProperty("domain"));
  driver.start();
  driver.open(properties.getProperty("domain"));
  driver.setSpeed("500");
 }
 
 public void closeDriver(){
  driver.stop();
 }
  
 public boolean ifXPathExists(String xpathExpression){
  try {
   lookupXPathExists(xpathExpression);
  } catch (AutomationException e) {
   return false;
  }
  return true;
 }
  
 private WebElementEmulator repeatableLookupExists(String xpath) {
  long start = System.currentTimeMillis();
  while (true) {
   try{
    if(driver.isVisible("xpath=" + xpath)){
     TTestCase.logger.debug(testCase.wrapMessage("Found xPath [" + xpath + "]. Creating object.."));
     return new WebElementEmulator(xpath, driver);
    }
   }catch(SeleniumException se){
    try {
     Thread.sleep(AWAITING_UNIT_LENGTH_MS);
     TTestCase.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.isElementPresent("xpath="+xpath);
   if (!isPresent) {
    Statistics.totalLatency += System.currentTimeMillis() - start;
    TTestCase.logger.debug(testCase.wrapMessage("Not found xPath [" + xpath + "]. Success.."));
    return true;
   }
   else{
    if(!driver.isVisible("xpath="+xpath)){
     TTestCase.logger.debug(testCase.wrapMessage("Found xPath [" + xpath + "]. However it is not visible. Success.."));
     return true;
    }
    try {
     Thread.sleep(AWAITING_UNIT_LENGTH_MS);
     TTestCase.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;
 }

 public WebElementEmulator lookupXPathExists(String xpath) throws AutomationException{
  WebElementEmulator handledElement = repeatableLookupExists(xpath);
  if (handledElement!=null) {
   if (handledElement.getQuantity()>1){
    throw new AutomationException("Several elements can be located using specified xpath [" 
      + xpath + "] You should concretize.");
   }
   return handledElement;
  } else {
   throw new AutomationException("Lookup for element ["
     + xpath + "]" + " failed after "
     + AWAITING_THRESHOLD_MS + " ms awaiting.");
   
  }
 }
 
 public boolean lookupXPathDoesNotExist(String xpath) throws AutomationException {
  if (repeatableLookupDoesNotExist(xpath)) {
   return true;
  }else{
   throw new AutomationException("The xpath ["+xpath+"] is still observed after "+AWAITING_THRESHOLD_MS + " ms awaiting.");
  }
 }
}

That's pretty much it.