Friday, June 5, 2009

GXT 2.0 and Selenium - Part 1

For those of you who follow the GXT toolkit know, they are currently in the process or preparing for the release of the 2.0 version. I converted to the 2.0 M1 version a few weeks ago and other than a few issues, have been able to continue using my application (and building on it). However when I upgraded to the 2.0 M2 version, I had several key areas that no longer worked. Of course, while I had unit tests to test my services, I didn't have anything for my GWT-based client. At this point I rolled back any updates I had made to be compliant with 2.0 M1, and started to write some Selenium tests to cover some of the key functionality. This should help my conversion to 2.0 M2 later. Through the use of Selenium I have come across a few tips and techniques I thought I'd share:

1. Be liberal with pauses (just be smart about it!)
Ideally you should be able to type, click and type, but the nature of GXT being a web application means that there are many transitions that occur, elements need to be built/loaded etc. Do not assume that after clicking an action to show a dialog that the dialog is immediately there. To handle this I do two things: First, ensure that the element is present, and second that the element is visible. Since the element may take a while to materialize you should wrap this in a loop for a predetermined time and break when both conditions are met (I generally use 1.5-10.0 seconds as the maximum timeout waiting 100-250ms between checks). You need to use your judgement based on the complexity of the elements you wait for (for example updating a status bar may be immediate, but creating a complex form (for the first time) might require creation of models etc.) You need to check for the element being present first, because the call to selenium.isVisible( ) will throw an exception if the locator is not yet in the DOM. Here is a simple example from one of my Selenium solutions:
for(int i = 0; i < 10; i++ ) {
   if( selenium.isElementPresent(itemLocator) && selenium.isVisible(itemLocator)) {
      selenium.mouseDown(itemLocator);
      break;
   }
   SeleniumHelper.pause(250);
}

2. Keystrokes and GXT
For text input, Selenium provides two methods: type() and typeKeys(). The later supposedly emits keystroke events as the keys are typed in sequentially (whereas type() acts more like a paste of a set value). As I posted previously in Selenium with GXT..., the problem with these methods and GXT is they do not seem to play very well together. I have not done too much data entry testing yet in Selenium, but I have cases under test and only one of the three worked using type() without changes. The two that didn't work consisted of a StoreFilterField to filter my tree view, and the other were simple form fields in a window.

For the StoreFilterField, it was essential that as each keystroke was made appropriate events were fired to cause the filtering to be invoked. In my case, it wasn't important to have the events be emitted with each keystroke, just when I was done. What I discovered was that I had to manually emit the events after typing with a slight delay after the keys had been typed. From running these tests over and over, it would appear that a delay of approximately 250ms is sufficient between the typing and the event emitting. Keep in mind, if you use typeKeys( ) this is supposedly already emitting these events. What is important is to emit them again. This is what seemed to trigger the filter to fire. Within Firefox, you need to emit three key events: keydown, keypress, keyup in that order. As well, as I will discuss next, form field were only populated in GXT with a blur event, so I also fire the blur, focus events. While this might seem kind of over-complex, it does work, and my tests have been running smooth (and passing) since. I wrote a method which I put into one of my abstract solutions called textWithEvents. Lets look at the code:
public void textWithEvents( String locator, String text ) { 
    for( int i = 0; i < text.length(); i++ ) {
        selenium.type(locator, text.substring(0,i+1));
        fireKeyEventSequence(locator);
        SeleniumHelper.pause(25);
    }
    SeleniumHelper.pause(225);
    fireKeyEventSequence(locator);
}

protected void fireKeyEventSequence(String locator) {
    selenium.fireEvent(locator, "keydown");
    selenium.fireEvent(locator, "keypress");
    selenium.fireEvent(locator, "keyup");
    selenium.fireEvent(locator, "blur");
    selenium.fireEvent(locator, "focus");
}

Now one thing you'll note is that I am using type() and I am actually re-typing each character. I could have typed the whole thing, but then this would not have fired the events as I was typing (which some code may require). But why not typeKeys? I can not explain this yet, but typeKeys() leads to invalid strings not matching the input text. I had a test failing over and over for a while and it was because of the use of typeKeys() in a StoreFilterField. What was happening was while trying to type in the text "My Test_233333--2", the result was "MTest_233333". Somehow typeKeys() was dropping characters or invoking other characters (such as a backspace) instead. I was at a complete loss. When I came across this in disgust (having lost almost an hour trying to figure out the problem - I thought it was related to locators originally) I decided to switch to type() even though it is a little slower. If you are filling out simple forms you will not really notice more than a few extra milliseconds. For typing large amounts of text in a text area, I would recommend a hybrid of the above method, likely simply using type() for the entire text and then double emit the key events with a pause between (unless you need to test UI interaction with each keystroke).

Finally, the reason for the blur and focus events was to ensure the form fields were populated. In GXT, form fields like TextFields do not store their value in the input HTML components, but internally (I assume as a variable in Javascript). You can see this watching the changes to the DOM using Firebug with Firefox. For this reason, to ensure the values are updated firing the blur event, will update the GXT component value. The focus event was fired just to maintain focus in the component.


I intend to continue this topic to cover some other areas I have discovered (such as smart locators and ComboBoxes) in an upcoming article. I felt this article was getting a little long. Stay tuned!


Version Info
GXT: 2.0-M1
Selenium: 1.0-beta2

3 comments:

Carl said...

Jason,

I've found that with the right locators and wait for conditions you should not need to use pause. Pause gives me a bad feeling that in some scenario the timing will be off and the test will fail.

Also without pauses the tests runs as fast as it can.

Carl said...

Just to clarify:

I use selenium Wait() with a timeout:

public static void waitForElementPresent(final Selenium selenium, final String locator, int timeout)
{
new Wait() {
public boolean until() {
return selenium.isElementPresent(locator);
}
}.wait("Error: Element with locator '" + locator + "' not found", timeout);
}

lvojnovic said...

where is part 2? :)
I have a lot of problems trying to deal with comboboxes...