Showing posts with label GXT. Show all posts
Showing posts with label GXT. Show all posts

Thursday, July 15, 2010

Firefox, Selenium GXT and the nasty Quick Find Links Only

So I have seen this intermittent error with my selenium tests that drives me up the wall. I'll be running a test and all of a sudden a little "Quick Find" box shows up in the lower left corner in Firefox (not the same as CTRL-F). This appears to be some sort of "old feature" of firefox that is used to quickly find links or something in the page (people have complained about it since Firefox 1.0). If you google the term "firefox quick find links only" you'll see the issue. Basically if you are in a non-input field (like the page) and type either "/" or "'" it will invoke this. The problem is, in GXT with selenium in order for some values to be input, events need to be raised by my selenium tests to blur/focus etc. Even though I am not typing in an apostrophe or slash, some sequence will trigger this control. Usually it was during the typing in a text field after clearing it and the first character (or in the case of my tree filter the 8th character?) would be dropped. It turns out, a solution that appears to work is the use of a Firefox plugin to turn off this widget/control (not sure why there is not a setting to do it by default - since I am sure most people do not even know the functionality is there).

There is a solution for this, but in order to use it you'll want to setup Firefox and Selenium to use a custom profile (since you have to install this plugin). To do so, start firefox with the -profileManager argument (ie. firefox -profilemanager). Be sure not to have any instances running when you do this (or else it will be ignored). When the profile manager shows up:
  1. uncheck the "don't ask at startup" - we'll restore this later
  2. choose to create a new profile
  3. give your profile a name like "selenium" and click the choose folder. Before going further write down this path as you will need it along with the profile name. On Windows 7, mine would be c:\users\myusername\AppData\Roaming\Mozilla\FireFox\Profiles.
  4. Launch Firefox using this profile.
  5. Go to your favorite website (like my blog). Without clicking in a textfield, hit the button "/". If you see a Quick Find show up, the next should help disable this.
  6. The process to disable the Quick find is documented here. In particular you'll need to download the plugin and install it to Firefox (on Windows is .xpi is not mapped to firefox choose to pick a file and choose firefox). Once installed, follow the instructions.
  7. After "disabling" it, try step 5 again and this time the Quick Find should not show up.
  8. Now you have a Firefox that will run the selenium tests without Quick Find, but selenium is not using it. The next step is to modify your selenium startup script (if you are running it by typing java -jar selenium-server.jar write a script!. In the script, add the flag -firefoxProfileTemplate path_to_profile_above\profile_name to the end of the command. This will use your custom profile for executing the tests.
  9. Finally, close down firefox and launch it again. This time checkoff the "Don't ask at startup" and choose your regular profile and you are off back using firefox with your profile.

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

Monday, January 25, 2010

Selenium and GXT CheckboxListView

I recently updated my application to use a menu hooked up to a button in which I display a GXT CheckboxListView of stamp issues. The general idea is that when defining my stamp if there are flaws (I tend to avoid collecting these) I can check-off the flaws in a drop-down like list. I added some selenium tests to capture the correct functionality, and came a across a few interesting points I thought I'd share.
  1. GXT does not appear to let you set a constant on the checkbox, such that can you tie it back to an enumerated list easily. This meant I had to navigate to the text of interest (localized display value of the enum) and then proceed back in the DOM to get the input field such as the following:

    MENU_LOCATOR + //td[.='" + display_str + "']/../td/input[contains(@class,'x-view-item-checkbox')]
  2. For closing the menu, the best approach I found was to use the "Escape" key on the menu itself:

    selenium.keyDown(PICKER_LOCATOR, "\u0027");
    selenium.keyPress(PICKER_LOCATOR, "\u0027");
    selenium.keyUp(PICKER_LOCATOR, "\u0027");
  3. Setting the value of the checkbox turned out to be more than simply using click(). In order for the click to be registered with the underlying GXT models I also needed to emit a DOMActivate event on the checkbox:

    selenium.check(getCheckboxLocator(str));
    selenium.fireEvent(getCheckboxLocator(str),"DOMActivate");

Version Info
GXT: 2.1.0
GWT: 2.0.0

Sunday, December 13, 2009

Upgrading to GWT 2.0 with GXT 2.1.0

I had to upgrade my GWT version to 2.0.0 with GXT 2.1.0. For the most part this was pretty straight forward. One change that impacted me was the usage of ImageBundle being deprecated. To convert it to the GWT version of doing things I had to do the following:
  1. extend com.google.gwt.resources.client.ClientBundle instead of ImageBundle
  2. replace @Resource with @Source (from ClientBundle)
  3. the return types will need to change to com.google.gwt.resources.client.ImageResource for each of the image methods.

Instead of using the interface directly (after being runtime binded with GWT.create()), you will need to convert the ImageResource to an AbstractImagePrototype. This can be done by calling the create(ImageResource) method.

The thing that is unfortunate about this, is all my setIcon() calls simply referred to a singleton instance of the images. Now they all need to convert to via the create() call. Hopefully I can work out a nicer solution for the future.

The article that helped guide me in this came from the google wiki: ImageResource

Version Info
GXT: 2.1.0
GWT: 2.1.0

Friday, December 11, 2009

GXT 2.1.0 Conversion

The following is a list of the issues I ran into when I converted from GXT 2.0.1 to GXT 2.1.0. This is not the complete exhaustive list, however these may provide others with some of the workarounds.

  1. My dialogs will cascade events to the form panels and their contents on the dialog. A change was made to Layout which requires all components which are receiving the event to have a layoutData defined. Since dialogs would normally not hold a layoutData (in my case they were using FitLayout which results in a null layoutData), I had to change the cascading to start at the dialog's FormPanel.
  2. All text input components have the wrapper set to the id used to create the component (this should be a <div/> tag). The physical input field now has a "-input" added to the end of the id. This impacted my Selenium tests.
  3. The StoreFilterField required the setProperty("name") to be called in order for the filtering to actually be invoked. I also changed my overloaded applyFilters() method to be similiar to the super method in the way it adds/removes the filter on the store.
  4. @Override
    protected void applyFilters(Store<ModelData> store) {
     if (getRawValue().length() >= 3) {
      store.addFilter(filter);
         store.applyFilters(super.getProperty());
        } else {
         store.removeFilter(filter);
        }
    }
  5. In my country ListView, when the Events.Select was raised I was retrieving the selected countries from the selection model. This either changed or I was doing it incorrectly, as now the selection model was returning the items before the selection is registered. Instead, I switched to use the getIndex() of the ListViewEvent to find the country that was selected.
  6. For my image panel, I was using a GWT HTML component. This now required the parent to implement HasWidgets interface, which the ContentPanel in GXT doesn't (since all GXT components extend Widget from GWT). Fortunately there is a GXT component Html which does pretty much the same as the GWT one, and converting to use this worked.
  7. Nodes in a TreePanel had their rendering changed from a <li> tag to a <div> tag. This impacted Selenium tests.
  8. Popup menus changed from using <li> tags to using <div> tags. This impacted Selenium tests.

Overall, the changes took me approximately 5 hours to incorporate and resolve the differences in my selenium tests. Some of it was due to changes in the rendering of the GXT components (which is to be expected and is not difficult to reconcile) and some of it was related to event sequencing and component model changes. It was these later changes which impacted my time the greatest.


Version Info
GXT: 2.1.0
Selenium: 1.0.1

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

Thursday, May 28, 2009

Selenium with GXT: getting textfields to emit those key events

In my Stamp Editor I have a tree browser which can browse collections, albums and countries. Directly above this, I have a StoreFilterField in which I can type text to filter the tree. I wanted to write a few selenium tests for this functionality to verify the items are found. The problem I ran into is when I told selenium to type the text, the text would appear but the filter would not get invoked. I knew this had something to do with the events being emitted so I tried to figure out what was going on. It turns out, it appears to be a timing issue. A great link which discusses the Javascript events (and browser differences) is here. Since the only selenium browser I am supporting for now is Firefox 3.X, the events I needed to emit to tell the textfield to start filtering was:
  1. keydown
  2. keypress
  3. keyup

The problem was, is even after emitting these events, the filtering was not getting invoked. After playing around in Firebug, I discerned that I needed to "refire" these events after a point of time. I wrapped this into a common method of my selenium solutions such that the code now looked like this:

public void textWithEvents( String locator, String text ) { 
   selenium.typeKeys(locator, text);
   SeleniumHelper.pause(500); // give events time to breathe
   selenium.fireEvent(locator, "keydown");
   selenium.fireEvent(locator, "keypress");
   selenium.fireEvent(locator, "keyup");
}



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

Tuesday, December 2, 2008

GXT Tree and Loading ... Alternative Design

This is a followup blog to my previous blog entry on GXT Tree and Loading from Restful URLs. While this approach worked, I had concerns over the number of AJAX requests as the tree is fully "expanded". As well, I was considering another possible issue:
  • The BaseModel objects representing the Country, Album objects are used elsewhere in the client (not just within my tree).
In this sense, the tree model is really a "view" into the real data. Having the tree be dynamically loaded prevents me from really being able to easily sync up changes made to the real data used elsewhere. I had already recognized that I likely needed to provide a singleton store pattern for obtaining countries and albums. The only problem was I was using this for lists and selection drop-downs which are modeled as ListStores (which co-incidentally are incompatible with TreeStores). However the fact that I have data being manipulated in two places just seems rather dangerous to me. For this reason, I think the process I intend to use here is still to use a DataProxy for the TreeStore, but instead have it's load method pull values from the corresponding singleton CountryStore and AlbumStore, and then provide some form of a Binder such that I can bind my TreeStore to the CountryStore (thus when a Country is created, deleted or modified, the TreeStore will be notified of the change in the CountryStore and take appropriate action). This also means I might be able to use a lighter-weight "representation" of the Country in the Tree (for example, just the "name" (for display) and "id" (as the foreign key to the CountryStore). In this case, my tree would really boil down to a BaseTreeModel object with three attributes ("name", "id", "type"). I'd want the type so that I can:

  1. Know the singleton store to access (if I need to full model)
  2. Toggle/display to proper icon in the tree
  3. Handle the leaf/childless cases (such as for Country)
One challenge in this case is I would need to ensure the AlbumStore, CountryStore and StampCollectionStore's are loaded prior to initializing/loading the tree store. As well, I would need to make sure the JSON output from the Album and StampCollection Restful Web Services includes an array of the ids of their children. (It actually is doing this today - but I bring this up as a point that others might consider). For example, an Album JSON String might look like the following:

{"total":1,"albums":[{"name":"Australia","id":154,"countries":[1104,733,3851,704]}]}
In the above example, the album for Australia contains references to the countries 1104, 733 etc. If the StampTreeDataProxy came across a parent BaseModel which was an Album reference, I could obtain the concrete Album from the AlbumStore to get the Country references and thus create an output list for the Callback.onSuccess() method.

While this above approach will take me more time to implement, I think it will provide one major performance improvement. The tree building time is strictly driven by tree AJAX calls to obtain the concrete Country, Album and Stamp Collection data models, as opposed to "N" calls for each node in the tree. It would also give a consolidated view of the tree and allow management of the models to be controlled by their respective stores in a more uniform fashion.

Finally, using a ReferenceModel approach, I actually do not even need the DataProxy. I'd have to test this out, but it looks like I can simply use the TreeBinder to bind a Tree to a TreeModel, and the TreeModel can be built programatically once the singleton data stores are created.