GWT, Gxt and Selenium Testing

If there is one thing I've learned about using Selenium to test gxt applications, is that there's a lot of pain and blood involved.

I've spend the best part of 5 days working out how to use Selenium Java WebDriver, to test a very complicated application. An application that included Dialog windows, ToolBars, Wizard Dialogs, and the usual array of Ajax, forms, grids and tables.

The journey started by having to understand how GXT renders JavaScript into HTML pages. When you are want to set element IDs in your code against most objects (like Button) - guess what, they don't appear on the <button> tag at all.

The next step is getting the Firefox WebDriver to work with the GWT Development Mode plugin for Firefox to work seemlessly.

In this artical I will divaluge all these secretes and provide you with a downloadable Maven project so you can see extactly what I've done.

But, firstly, to give a bit of context, I want to breafly mension the structure of the Selenium tests.

The tests use the java WebDriver and JUnit to simulate user interaction. The structure of the Seleiumn code uses the Page Object Model which means that each part of of the web application to be tested will have a corrisponding Selenium Page Object. So for example the ToolBar of the Gxt application will have a ToolBarPageObject which has methods for interacting with the toolbar (clicking buttons etc).

Buy combining these Page Objects together you can test your application much more effectivly and maintainably than using the Selenium UI to record scripts. This is because if you change something on your web app (say the toolbar for example), then, using Selenium UI your entire script will be busted. Whereas if you used Page Objects you only need to update the page object representing your toolbar - ToolBarPageObject.

This is just a bit of usefull background info for when you look at the source code downloadable at the bottom of this page.

So to start:

How Gxt Renders IDs on HTML <Form> Elements

OK, so the first and possibly the most supprising thing you need to know, is that when you try and put an ID on a form element such as with :

TextField<String> nameField = new TextField<String>();
nameField.setId("MY_ID");

The ID does not, contrary to what you might expect, get put on the corrsponding <input> HTML tag, but gets assigned to a <div> tag which encloses the <input> tag.

Here is the output from gxt:

<div role="presentation" class="x-form-item" tabindex="-1">

    <label style="width:75px;" class="x-form-item-label">First Name:</label>

    <div role="presentation" class="x-form-el-MY_ID" id="x-form-el-MY_ID">

        <div role="presentation" class="...." id="MY_ID" style="...">
            <input type="text" class="..." id="MY_ID-input" style="...">
        </div>

    </div>
    <div class="x-form-clear-left" role="presentation"></div>
</div>

So this is tip No. 1:

  • Just because you specify a ID on a object in gxt does not mean that the ID will render to the most oviouse corrisponding element in the output HTML

However, there is more. As you may have noticed from the highlighted sections above, the ID that does get put on the input tag gets appended with "-input".

Now, I'm not sure why this is but I would guess its probably because the guys at Sencha are not the brightest cookies in the tin, and didn't realise that some people may want to identify certain obvious elements with the ID tag they specified in their code. But there again, I suppose you could argue that people who want to develop web pages via a Java Swing type of framework (e.g. using LayoutManagers), arn't the brightest lights either.

But to be a little bit fair, they do render the TextField object into a nicely formatted label and input field combination. So maybe they just got confused because they were a bit drunk and pulling a late one on Friday night.

So Tip No. 2:

  • Just because you specified your ID as ID_ABC does not mean that it will render in HTML (or the DOM if you're being picky), as ID_ABC.

Bonus Tip:

  • If you are going to use GXT then add 40% to your estimates.

So where does your ID go? Well if you specify an ID on a Button object:

Button button = new Button("Click Me!");
button.setId("BUTTON_ID");

It gets put on the enclosing table object! Weird hu?

Here, with the intresting bits in blue, is how GXT renders the Button class to HTML:

<table cellspacing="0" role="presentation" id="BUTTON_ID" class=" x-btn x-component x-btn-noicon ">
    <tbody class="x-btn-small x-btn-icon-small-left">
    <tr>
        <td class="x-btn-tl"><i>&nbsp;</i></td>
        <td class="x-btn-tc"></td>
        <td class="x-btn-tr"><i>&nbsp;</i></td>
    </tr>
    <tr>
        <td class="x-btn-ml"><i>&nbsp;</i></td>
        <td class="x-btn-mc">
          <em class="" unselectable="on">
            <button class="x-btn-text " type="button"
                   style="position: relative; width: 60px; " tabindex="0">
               Click Me!
            </button>

          </em>
        </td>
        <td class="x-btn-mr"><i>&nbsp;</i></td>
    </tr>
    <tr>
        <td class="x-btn-bl"><i>&nbsp;</i></td>
        <td class="x-btn-bc"></td><td class="x-btn-br"><i>&nbsp;</i></td>
    </tr>
    </tbody>
</table>

How to Use the Firefox WebDriver

Firefox is the best browser for running Selenium tests. This seems to be because it's open source so gets the best support.

When GWT runs in development mode, the browser you use to view the app needs the GWT plugin. However, if use just use the FirefoxDriver with Selenium, you will find it starts a copy of vanilla Firefox that does not contain the plugin.

So to get Selenium to work with Firefox must first make sure you have the plugin installed in your Firefox browser and then you must create a Firefox profile. The way to create a Firefox Profile is explaned here. But the short answer is to run:

    firefox.exe -p

from the command line. Then use the dialog box to create a profile. Save the profile to a convenient directory for your tests. The profile will contain all the plugins your copy of Firefox has, and some settings info, so before doing this you should disable or remove any plugins that are not relevent (e.g. Adobe), and disable the automatic updates (otherwise every time you run your tests the browser will tell you it needs to be updated).

So now you can start using the FirefoxDriver to drive your tests:

final File profileDir = new File(FIREFOX_PROFILE_DIR);
final FirefoxProfile firefoxProfile = new FirefoxProfile(profileDir);
final WebDriver driver = new FirefoxDriver(fp)
...

So now you should be able to start runing your tests...

If only it was that easy!

The Gxt Selenium Tests

So as you are probably aware if you have tried to run Selenium tests against Gxt, things are not as easy as they may seem. So here are some tips to get you started.

  • Make sure you use the Page Objects pattern.
  • Whenever you've defined a LayoutContainer that you want to run some tests against, make sure you give it a unique ID.
  • Make sure you give every form, form field, button, menu option etc, a unique ID.
  • Try to avoid using EditorGrid whenever you can. This is because its very hard to get working reliably, but I will show you what I did just incase you are crazy enough to try.

Accessing Form Fields

Buttons:

Gxt Code:

final Button button = new Button("Click Me");

button.setId("click-me-button");

Selenium Code:

@FindBy(id = "click-me-button")
private WebElement clickMeButton;

...

@Test
public void testButton()
{
    clickMeButton.click();
}


TextField, NumberField, etc:

Gxt Code:

final TextField<String> nameField = new TextField<String>();
nameField.setId("name-field");
nameField.setFieldLabel("Name");
...

Selenium Code:

// Note the addition of "-input" which is added by gxt.
@FindBy(id = "name-field-input")
private WebElement nameField;

...

@Test
public void testField()
{
    nameField.click(); // See note below
    nameField.sendKeys("Adam Davies");
}

Note: the click() call is not strictly required, but the user will either click or tab to the text field so its best to do that in your tests just to ensure that any corrisponding javascript events are fired.


ComboBox:

Gxt Code:

// CountryCombo is a subclass of SimpleComboBox<String>
final CountryCombo countryCombo = new CountryCombo();
countryCombo.setId("country-combo"); 
...

Selenium Code:

@FindBy(id = "XXXX")
private WebElement countryCombo;

...

@Test
public void testCountryCombo()
{

}

 

EditorGrid Cells:

For EditorGrid cells is a bit more difficult, but generally you have to get the DIV that the cell relates to, click it, which results in gxt generating a edit field (e.g. a TextField), and then you can get the active Field by the class name x-form-focus. It seems that gxt in all it's wizdom put that class against the <INPUT> element when generating an edit field.

The following code demonstates this techique:

 

Note: The generall format that gxt uses to identify cells in a grid is something like this:

x-grid3-col-name row1-col2 row1-col3
row2-col1 row2-col2 row2-col3
row3-col1 row3-col2 row3-col3

Downloads

 

Add comment