Friday, March 5, 2010

GXT and Selenium - Part 2

First off, to all those who have patiently asked for my solution of GXT combo boxes and selenium, I do apologize for the long time in coming. While reading my articles is certainly not like being left with a cliff hanger in a movie sequel, I know the pain in waiting anxiously for a solution.

Combo Boxes
After trying many different ways to get combo boxes to work, I have worked out a pattern which I will share which has seemed to provide the greatest stability in our tests. We currently have forty-odd selenium tests run upon check-in to trunk for the stamp webeditor project. Approximately half of them utilize one of the dozen or so combo boxes in my application and the only failures we have seen is when there is actually something wrong with our underlying code.

Here is a break-down of the steps:

  1. If a label is provided (ie. a combobox with a label), attempt to wait for the combobox to be enabled. When the combobox is first shown, the backing store may be initializing or other activities may be happening, thus causing the combobox to be disabled. The routine will simply wait a duration for a given number of cycles for the combobox to be enabled.
  2. The combobox will be clicked.
  3. If the combobox results are visible, and one of those results is the item we are looking for, a mouseDown event will be emitted on the resulting item. If not present, a system out message will be printed (I found this useful in debugging the issue. In practice I may only see the message once if the system is bogged down and running slow)
  4. I found a need to wait another duration after the mouseDown event, proceeding with firing off a blur event. While these last may not always be needed, as I mentioned above, I have not had any failures show up due to combobox selection and having these in the code certainly have helped!

I should mention the waitDuration and waitCycles are simply integer static values (125 and 20 respectfully however I can configure these via configuration). Finally the pause() method is simply a Thread.sleep() call wrapped in a try/catch block. One of my readers Carl, suggested using the Selenium.Wait( ) functionality, which I may explore in the future.

So lets look at some code:

 /**
  * Will select the text in a ComboBox by the specified locator.  If the ComboBox is provided
  * a label, the method will try and determine if the ComboBox is disabled, and if it is wait
  * for it to become enabled (or timeout after 5000 seconds whichever is first).
  * 
  * @param selenium The selenium context
  * @param text  The text to pick from the selections
  * @param locator The locator of the selection input box
  * @param label  The label for the ComboBox (optional)
  */
 public static void selectByText(Selenium selenium, String text, String locator, String label) {
  if( label != null) {
   String disabledLocator = "//fieldset//label[.='" + label +":']/following-sibling::div//div[contains(@class,'x-item-disabled')]";
   int timeout = 0;
   while( selenium.isElementPresent(disabledLocator )) {
    pause(waitDuration);
    timeout++;
    if( timeout > waitCycles) {
     throw new SeleniumException("Selection element was not enabled after timeout");
    }
   }
  }
  boolean found = false;
  String downDownLocator = "//div[contains(@class,'x-combo-list')]";
  String itemLocator = downDownLocator + "//div[contains(@class,'x-combo-list-item') and .='" + text + "']";
  selenium.click(locator);
   for(int i = 0; i < waitCycles; i++ ) {
    if( selenium.isElementPresent(itemLocator) && selenium.isVisible(itemLocator)) {
     selenium.mouseDown(itemLocator);
     found = true;
     break;
    } else if ( selenium.isElementPresent(downDownLocator) ) {
     System.out.println("selectByText() - at least div for the combo is present...");
    } else {
     System.out.println("selectByText() - no div for the combo present.");
    }
    pause(waitDuration);
   }
 
  if( !found ) {
   throw new SeleniumException("locator:" + itemLocator + " not found.");
  }
  pause(waitDuration);
  selenium.fireEvent(locator, "blur");
 }
 

Hopefully this can help alleviate some of the pain out there around the GXT combo boxes and selenium.


Version Info
GXT: 2.1.1
Selenium: 1.0.1