Selenium

In a Nutshell

Selenium is an in-browser test tool for web integration tests. As has been pointed out, for web pages where the DOM of the response is not manipulated after the page has been served, tests don’t need to be in-browser and HttpUnit is a better solution. We needed Selenium four months ago to test the redrawing of a panel on the screen from an ajax call. (We’re not using Maven, but there is a Mavenuser page and a Raible blog entryabout integrating Selenium with Maven.)

Selenium was developed as an open-source project within ThoughtWorks, but now also has corporate backing: BEA has a couple of developers working on it full-time. [This is from Erik Doernenburg’s paper at QCon; other points from that paper will be noted ‘ED’.] There are several flavours of Selenium:

  • Selenium Core (write scripts in html tables which can be executed in browsers)
  • Selenium IDE (a firefox plugin which lets you record scripts for Selenium Core)
  • Selenium RC / Remote Control (build tests in Java code)

How to Add Selenium RC to a Project

  1. Download Selenium RC.
  2. Put the selenium-server.jar and selenium-client-java-driver.jar in your classpath.
  3. Start writing tests.

To access selenium in a test, you need to start up the selenium server on a port number and the selenium client (into which you can pass an argument for the browser, eg ‘*iexplore’, ‘*firefox’). You’ll probably want to define a SeleniumTestCase superclass that sets them up:

private SeleniumServer seleniumServer = null;
protected Selenium seleniumClient = null;

protected void setUp() throws Exception {
    super.setUp();
    this.seleniumServer = new SeleniumServer(PORT_NUMBER);
    this.seleniumServer.start();

    this.seleniumClient = new DefaultSelenium("localhost", PORT_NUMBER,
            "*iexplore", "http://localhost:8080/app");
    this.seleniumClient.start();
}

protected void tearDown() throws Exception {
    this.seleniumClient.stop();
    this.seleniumServer.stop();
    super.tearDown();
}

This is all you need to do with the selenium server; the selenium client, on the other hand, will be used in the tests for going to pages, interacting with them, and checking the results on them.

Performance: Because starting the server and client are expensive operations, if you have more than a few selenium tests you’ll probably want to wrap them in a separate suite and put these setUp() and tearDown() methods in a TestSetup decorator so that they only run once for the suite. [ED]

Writing Selenium Tests

The seleniumClient lets you manipulate the browser in fairly predictable ways, with commands like open({URL}), click({checkbox/button id}), select({dropdown id}, {selection}), type({textbox/area id}, {text}), etc. (API)

The thing we found most useful was adding lots of assert methods to our SeleniumTestCase superclass so that the tests themselves began to look like a mini-dsl, as nearly as possible readable by BAs or testers. For instance, for checkboxes we added:

protected void assertChecked(String id) {
    assertCheckboxState(id, "on");
}

protected void assertNotChecked(String id) {
    assertCheckboxState(id, "off");
}

private void assertCheckboxState(String id, String expectedValue) {
    assertEquals(expectedValue, this.seleniumClient.getValue(id));
}

Given enough scaffolding, if the clients come up with a new requirement, say a not-quite-standard behaviour for a tree of checkboxes, the test can easily be written with them sitting there and following along, and with a bit of coaching they could even write it themselves:

public void testCheckItemThenUncheckAndClearDisplayOnNextPage() {
    initializePage();
    click(PARENT_CHECKBOX);
    click(CHILD_CHECKBOX);
    click(PARENT_CHECKBOX);

    assertDisplayed(CHILD);
    assertDisplayed(GRANDCHILD);

    click(CHILD_CHECKBOX);

    assertDisplayed(CHILD);
    assertHidden(GRANDCHILD);

    open({another page in the app});

    assertHidden(CHILD);
    assertHidden(GRANDCHILD);
}

Once the behaviour is implemented, most or all of it should be testable and tested elsewhere in unit tests, of course, so parts of the test as a test may be redundant. But the test as a document arrived at by the developers and the client together is an unambiguous expression of the desired behaviour which everybody can sign off on. (And if it is Behaviour-Driven Development by the back door, I don’t have a problem with that.)

More from Erik Doernenburg’s QCon Paper

  • NB just because you’re writing a Selenium test doesn’t mean you need to do everything through the browser: setting up test data through app code will be quicker.
  • A class per test is a sensible idea. But if there are pages used by multiple tests, consider a separate hierarchy of “page” classes for reusable code. Use those “pages” in the tests for repeated actions, e.g.:
LoginPage loginPage = new LoginPage(this);
loginPage.login();
...
// stuff that's specific to the particular test

(pass in “this” to get access to the selenium instance, which the Page class will need to do its work)

It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply