News from February, 2010

  2010/02/05
PageObjects and how to make functional tests fun!
Last changed: Feb 23, 2010 09:03 by Luiz Ribeiro
Labels: testing, patterns

Writing and maintaining automated functional tests for web applications has always been a very complicated issue for us. We are usually between using Selenium or Canoo WebTest but regardless of the choice we seemed to always have to spend a lot of time fixing broken funcional tests and having trouble running them on continuous integration tools such as Cruise Control. Plus writing the tests itself was usually a very dull activity with all those xpaths and problems with Ajax calls, timers, etc.

Selenium offers a nice record-and-play feature but this didn't prove to be interesting either. The resulting test code is not very readable/maintainable and having to constantly re-record each test case and then re-tune it directly in the code was not very appealing either.

Nevertheless, we initially decided to create helper classes that would encapsulate the selenium or webtest calls such as click or type to more meaningful names like loginAs. This proved to be useful but still maintaining such tests was very time-consuming given that we had always to track down the failures and which methods in the helpers should be fixed.
At the same time, these helper classes would grow and become a mess as well as including many different levels of abstraction, for example, having methods like clickOnEditButton, which would just simulate a click, and createNewDocument, which would perform all the necessary actions to create a new document in our system. We finally found our way when we learned about the PageObjects pattern.

It consists of basically creating a class for each page on the system. Interactions with the page become methods in the class. If such interaction directs the user to a different page, like clicking on a link, the method returns a new object of the next correspondent page. The constructor of each page object class can also verify if the current page is indeed the one being created.

With this simple pattern we could change this:

Before
    helper.loginAsAdmin();
    selenium.clickAndWait("spacelink-" + helper.getSpaceKey());

    helper.createNewDocumentPage(helper.createPageName());
    helper.submit();

    assertEquals("test document content", selenium.getText("//div[@class='wiki-content']");

into this:

After using PageObjects
  @Test
  public void approveDocument() {
    CreateNewDocumentPage createDocPage = new LoginPage(helper)
        .loginAsAdmin()
        .goToUserSpace()
        .goToCreateNewDocumentPage();

    createDocPage.enterTitle("test document").
    createDocPage.enterContent("test document content");

    ViewDocumentPage viewDocPage = createDocPage.submit();
    assertEquals("test document content", viewDocPage.getContent());
  }

The improvement is clear!!!
The tests maintainability is much better since after changing some piece of code the developers know exactly which action in which page should be updated.
More than that, all the sudden writing functional tests went from a dull activity to something that is actually fun!

Page objects ftw!

Posted at 05 Feb @ 10:49 AM by Luiz Ribeiro | 3 Comments
  2010/02/19
WebDriver drives integration tests to the next level!
Last changed: Feb 23, 2010 09:04 by Luiz Ribeiro
Labels: testing, webdriver

WebDriver is a new framework by Google for writing web automated tests. It is being merged with Selenium by OpenQA into Selenium 2.0, but as of Feb 19th, 2010, it is already available at http://code.google.com/p/selenium/ while the development is still in progress. Some of the advantages of WebDriver are a better and object-oriented API, use of browser-specific mechanisms to execute the tests and better support for advanced cases like drag and drop and dealing with pop-ups or multiple windows.

Using WebDriver is simple, there's no need to start any server or anything. All that it takes is to instantiate and use any of the WebDriver interface implementing classes. There's a driver for each of the supported browsers (the current drivers are Firefox, Internet Explorer, Chrome and HTMLUnit):

Running WebDriver tests with Firefox
    WebDriver driver = new FirefoxDriver();
    driver.get("http://localhost:8080/mywebsite");
    driver.findElement(By.id("username_field"));

Each driver uses a different approach to simulate the user's interactions with the browser, contrary to Selenium that always uses Javascript. This means that the tests run in the most native way in each browser, which guarantees better performance and flexibility, but as the API is still the same, writing tests that have to run in many different browsers becomes simple
Currently, as I am writing this, the Firefox driver is the most advanced one in terms of implementation. You can check the development road map here: http://code.google.com/p/selenium/wiki/RoadMap

The WebDriver object-oriented API makes the code naturally more maintainable. Plus, it also makes it easier to access the web elements, their children and their properties, reducing the need to use xpaths:

WebDriver code example
    String username = "admin";
    String password = "admin";

    WebDriver driver = new FirefoxDriver();

    driver.get("http://localhost:8080/mywebsite");

    driver.findElement(By.id("os_username")).sendKeys(username);
    driver.findElement(By.id("os_password")).sendKeys(password);

    driver.findElement(By.id("loginButton")).click();

    assertEquals("Dashboard", driver.getTitle());

    WebElement recentTable = driver.findElement(By.id("recentlyUpdated"));
    List<WebElement> recentTableRows = recentTable.findElements(By.tagName("tr"));

    assertEquals(10, recentTableRows.size());

Notice how the findElements method searches for the elements children of the recentTable element.
It looks even better if used with the PageObjects pattern!

Talking about PageObjects, WebDrivers also offers a PageFactory class with auto binding for web elements, which makes it even more fun to use the pattern:

The same test case, using PageObjects and WebDriver's PageFactory
public class LoginPage {

  private WebDriver driver;

  @FindBy(id = "os_username")
  private WebElement usernameField;

  @FindBy(id = "os_password")
  private WebElement passwordField;

  // automatically binds by id using the field's name
  private WebElement loginButton;

  public LoginPage(WebDriver driver) {
    this.driver = driver;
    driver.get("http://localhost:1990/confluence");
  }

  public Dashboard login(String username, String password) {
    usernameField.sendKeys(username);
    passwordField.sendKeys(password);
    loginButton.click();
    return PageFactory.initElements(driver, Dashboard.class);
  }
}

public class Dashboard {

  private WebDriver driver;

  @FindBy(id = "recentlyUpdated")
  private WebElement recentTable;

  public Dashboard(WebDriver driver) {
    this.driver = driver;
    if (!"Dashboard".equals(driver.getTitle())) {
      throw new IllegalStateException("Dashboard page expected");
    }
  }

  public int getRecentlyUpdatedRowsCount() {
    List<WebElement> recentTableRows = recentTable.findElements(By.tagName("tr"));
    return recentTableRows.size();
  }
}

And our test:

    LoginPage loginPage = PageFactory.initElements(driver, LoginPage.class);
    Dashboard dashboard = loginPage.login(username, password);
    assertEquals(10, dashboard.getRecentlyUpdatedRowsCount());

Isn't it cool?

Get to know more about WebDriver at http://google-opensource.blogspot.com/2009/05/introducing-webdriver.html and http://code.google.com/p/selenium/
A more complete manual decribing also some more advanced features can be found here: http://seleniumhq.org/docs/09_webdriver.html

Have fun!

Posted at 19 Feb @ 5:10 PM by Luiz Ribeiro | 7 Comments
  2010/02/22
How to override Confluence's actions at runtime with XWork's configuration
Last changed: Feb 23, 2010 09:04 by Luiz Ribeiro
Labels: confluence

Recently we have been doing quite a lot of hardcore customizations on Atlassian's Confluence.
Often we find ourselves in a situation where we would like to have more control over the application's default behaviour. Confluence allows us to override some of the velocity files, or extend the default actions into our own custom actions, but some times these don't seem to be enough.

Suppose we want to override an action added by a third-party plugin or modify the flow of an existing action only for a given result.
We finally found out how to have full control over Confluence's configurations through XWork's configuration providers.
It is quite simple, but should probably be used with care.

Let's say we want to change the result of the default Confluence's edit page action.
All we have to do is, using the classes under com.opensymphony.xwork.config:


  String packageName = "pages";
  String actionName = "editpage";
  String resultName = "input";
  String newResultLocation = "/com/aqris/actions/document/newEditPage.vm";

  Configuration configuration = ConfigurationManager.getConfiguration();
  PackageConfig packageConfig = configuration.getPackageConfig(packageName);

  ActionConfig actionConfig = (ActionConfig) packageConfig.getActionConfigs().get(actionName);
  ResultConfig resultConfig = (ResultConfig) actionConfig.getResults().get(resultName);

  resultConfig.addParam("location", newResultLocation);


Now just modify the package, action and result names for those you want to modify and that's it.

In many cases creating a custom action that inherits from EditPageAction can be the most appropriate solution, but in some cases we might need some more advanced control over the system.
But never forget: with great powers come great responsibilities!

Posted at 22 Feb @ 12:49 PM by Luiz Ribeiro | 0 Comments