Tuesday, November 16, 2010

Using Selenium and Ajax on Richfaces

We use Selenium for functional testing of a Web Application based on Seam 2 and Richfaces. The test script typically invokes an action by pushing a button or clicking a link, which submits a form or issues an Ajax Request. Next you wait for the new Page to load or wait for an element to (dis)appear or a specific element value change. Then you assert the elements on the page to have the expected values.
Sometimes a Ajax request doesn't result in any change on the page or you don't want to include the expected page changes in the waiting code. On what condition do we wait before continuing. The easiest and also uggliest way is to insert a sleep. Problem is that we have to sleep long enough for the server to finish the ajax call. This is considered bad practice because now the test completion depends on  the hardware the test is executed and all tests will execute a considerable time longer than strictly necessary.
In our application we use the Richfaces a4j:status component (html is: _viewRoot:status.start) to show an indicator (shows text 'busy') on the page while the ajax request is being processed.  I created a waitForAjax method in our test base class which conditionally waits until the indicator is shown on the page and suqsequently waits until the indicator disappears again.
protected void waitForAjax() {
       waitForAnyText("_viewRoot:status.start", 5);
       waitForNoText("_viewRoot:status.start", 5);
}

protected void waitForAnyText(final String locator,int timeoutAsSeconds){
   waitFor(new WaitForCallback(){
     public boolean conditionMet(){
       return !selenium.getText(locator).equals("");
     }
   }, timeoutAsSeconds);
}

protected void waitForNoText(final String locator,int timeoutAsSeconds){
   waitFor(new WaitForCallback(){
     public boolean conditionMet(){
       return selenium.getText(locator).equals("");
     }
   }, timeoutAsSeconds);
}

protected void waitFor(WaitForCallback cb, int timeoutAsSeconds){
  for(int second=0;;second++) {
    if (second >= timeoutAsSeconds){
      fail("timeout");
    }
    if(cb.conditionMet()){
      break;
    }
    try {
      Thread.sleep(1000);
    }
    catch (InterruptedException e){}
  }
}
protected static interface WaitForCallback {
  boolean conditionMet();
}

The problem with this code is that if the server is really quick, the Ajax indicator is already gone when the script executes the waitForAnyText("_viewRoot:status.start") call, and the test will fail after the specified timeout. To fix this we need a non failing waitForAnyText().

protected void waitForAjax() {
       waitForAnyText("_viewRoot:status.start", 5, false);
       waitForNoText("_viewRoot:status.start", true);
}
protected void waitForAnyText(final String locator,int timeoutAsSeconds, boolean fail){
   waitFor(new WaitForCallback(){
     public boolean conditionMet(){
       return !selenium.getText(locator).equals("");
     }
   }, timeoutAsSeconds, fail);
}

protected void waitForNoText(final String locator,int timeoutAsSeconds, boolean fail){
   waitFor(new WaitForCallback(){
     public boolean conditionMet(){
       return selenium.getText(locator).equals("");
     }
   }, timeoutAsSeconds, fail);
}

protected void waitFor(WaitForCallback cb, int timeoutAsSeconds, boolean fail){
  for(int second=0;;second++) {
    if (second >= timeoutAsSeconds){
      if (fail) {fail("timeout");} else {break;}
    }
    if(cb.conditionMet()){
      break;
    }
    try {
      Thread.sleep(1000);
    }
    catch (InterruptedException e){}
  }
}
So, now after calling waitForAjax() from our test script we know for certain that the ajax call completed and we have waited only the minimum amount of time.

No comments: