PageObjects and how to make functional tests fun!

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!

Labels

testing testing Delete
patterns patterns Delete
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Feb 22

    Aleksandr Zuikov says:

    But what is this page? Is it the same code as in helper, selenium calls? Or is t...

    But what is this page? Is it the same code as in helper, selenium calls? Or is this page an actual object that you use in app?

    1. Feb 22

      Ürgo Ringo says:

      Actually this page should replace helper. I think helper is passed to the LoginP...

      Actually this page should replace helper. I think helper is passed to the LoginPage in this sample because we wanted to reuse existing (helpers') code when we started writing tests using PageObjects. Probably implementation of Selenium should be passed in case of Selenium framework.

  2. Mar 04

    Igor Malinin says:

    It looks like refactoring Helpers to Builder pattern and passing different Build...

    It looks like refactoring Helpers to Builder pattern and passing different Builders (Pages in this case) as a result of calls. Good idea .

    The only problem I see with this is how to handle cases where different pages could be returned as a result of a call (depending on some external conditions)?

    But, answering own question - for tests it should not be a problem as tests should generally have no external conditions anyway.

Add Comment