Home > Programming, Testing > Step-by-step selenium tests with page objects, dsl and fun!

Step-by-step selenium tests with page objects, dsl and fun!

Note: this is a long post!

Not long ago I wrote about functional tests and the page objects pattern in Aqris’ blog. Back then we at Aqris got all very excited about page objects as they were the solution we were looking for to solve our problems on maintaining our functional test code base, which by that time was based on a set of messy helpers that nobody really understood completely.

Before page objects, whenever a developer in our team had to write a test that had to perform an action no other test already performed, he or she would go through that bunch of helpers trying to figure out which one was the helper that should perform such an action. The result of this approach was that the team couldn’t get to a full agreement on what was each helper’s responsibilities. Everybody agreed that the code was not good, but each person had their point of view on why.

We first learned about the pattern here, when reading about WebDriver (now Selenium 2). Page objects came as the solution to separate the different actions our helpers contained in an extremely simple and even obvious way that nobody in our team had previously thought: simply creating a specialized helper for each page of the application.

It is in fact so simple that I still wonder how come we didn’t think of that before… I think that we were too busy trying to figure out how to deal with the helpers we had, and we were too used to have them that way. I guess that’s because the previous project we had worked on (and the first one we had a strong movement towards automated tests with Selenium) was a web app based on only one single page with lots of GWT-based ajax.

Anyway, excuses aside, we started using page objects and it was great! But then other doubts started to come up: how to deal with parts of a page that are common to many pages – for instance a side navigation bar? How to make our tests more readable? Should our page objects use selenium directly? If yes, how to resolve the selenium dependency? Can the page objects just encapsulate the locators for the html elements instead? Should page objects be responsible for the tests assertions too, depending on xUnit’s API, or should they just provide verification methods that would be used by our tests’ code itself?

I think that these are questions that may or may not have a straight correct answer, but here I will write a bit of what worked well for us or for me later on when dealing with that.
To do that I think we can write a test for an imagined scenario.
Let’s try that!

The problem

Let’s say that we have a test case to test the following hypothetical scenario:

We have a hypothetical book store application.
Every page in the application has a navigation bar on the side.
An user goes to our application home page and, by clicking on a link on the navigation bar, she goes to a page to search for books.
On the search books page she fills in a search form, entering “Tolkien” in the author field and “Rings” in the title field, and submits the form.
She is then redirected to a search result page that contains a list of books along with the same search form already filled in with the same search data she had entered – in this case, “Tolkien” in the author field and “Rings” in the title field.

We want to assure that, given our test data, the search result contains the book ‘The Return Of The King’.
We also want to assure that the search form in the result page still has the data she had previously entered.

The code solution after the jump :)

The code

(I will write the code in Groovy here, because it is similar to Java, which is a widely known language, and because with Groovy we can do some nice things to make the code more readable. But you can use whichever language you prefer.)

Usually we create a Page class which all other page objects classes inherit from.
It keeps a reference to our selenium instance, so that all our pages know how to manipulate their elements and do all the dirty job for us, exposing higher-level actions to our tests. Plus, if the HTML elements on our page changes, we know exactly what part of our test code base has to be updated.

So our Page class may look like this:

class Page {
  Selenium selenium   
  Page(Selenium selenium, String title) {
    this.selenium = selenium

    /*
     * In Groovy, selenium.title is the same as selenium.getTitle()
     */
    if (title != selenium.title) {
      throw new IllegalStateException("Should be on page ${title} but I am actually on page ${selenium.title}")
    }
  }
}

The Page constructor receives the expected page title, which is passed by the subclasses that will inherit from it.
Then we check if the title of the page in which selenium currently is corresponds to the title of the page our test should be. This is a nice way to ensure the test is navigating through the pages following the flow we expect it to.
It is a bit annoying to pass the selenium reference to every page we create, but I think that it is worth it anyway.

I also like to create Button, Link and Form classes, as usually we have to deal with all of them and specially because from the interactions with these elements our test navigates among the pages, so they can create new page objects for us too.

They may look something like this:

class Button {
  Selenium selenium
  String locator

  Button(Selenium selenium, String locator) {
    this.selenium = selenium
    this.locator = locator
  }

  void click() {
    // sometimes Groovy allows us to omit the parenthesis!
    selenium.click locator
  }
}

The locator attribute is used by selenium to find the button in the page’s HTML structure. It might be an ID, an xpath expression, etc.
I made Link an abstract class because each link will bring a different page to the user:

abstract class Link {
  Selenium selenium
  String locator
  
  Link(Selenium selenium, String locator) {
    this.selenium = selenium
    this.locator = locator
  }

  Page click() {
    selenium.click(locator)
    return linkedPage // the same as 'return getLinkedPage()'
  }

  abstract Page getLinkedPage()
}

Similarly, forms are also represented by abstract classes. They know how to fill themselves with data using selenium, and which page to show after they are submitted:

abstract class Form {
  Selenium selenium

  Form(Selenium selenium) {
    this.selenium = selenium
  }

  Page submit() {
    submitButton.click()
    return pageAfterSubmit
  }
  
  abstract void fill()

  abstract Button getSubmitButton()

  abstract Page getPageAfterSubmit()
}

With this we can start to write the classes specific to our scenario.

As all pages in our application contain the navigation menu, we can modify the Page class so that it contains an instance to a NavigationMenu object, which will keep the details of the actions related to the menu:

class Page {
  Selenium selenium
  NavigationMenu navMenu
  
  Page(Selenium selenium, String title) {
    this.selenium = selenium
    if (title != selenium.title) {
      throw new IllegalStateException("Should be on page $title but I am actually on page $selenium.title")
    }
    navMenu = new NavigationMenu(selenium)
  }
}

class NavigationMenu {
  Selenium selenium

  NavigationMenu(Selenium selenium) {
    this.selenium = selenium
  }

  Link getSearchBooksLink() {
    return new Link(selenium, "link=Seach Books") {
      Page getLinkedPage() {
        return new SearchBooksPage(selenium)
      }
    }
  }
}

Notice that the navigation menu has a searchBooksLink, which when clicked returns an object for the search page.
The HomePage class that represents our application’s first page is as simple as it gets:

class HomePage extends Page {

  static final String URL = "http://localhost:8080/my_book_store"

  HomePage(Selenium selenium) {
    super(selenium, "My Book Store")
  }
}

So we can start to write our test code:

def testShouldFindTolkienBooks() {
  selenium.open HomePage.URL
  def homePage = new HomePage(selenium)
  def searchPage = page.navMenu.searchBooksLink.click()
}

The SearchBooksPage class contains our SearchBooksForm which inherits from Form:

class SearchBooksPage extends Page {
  Form searchForm
  SearchBooksPage(Selenium selenium) {
    super(selenium, "My Book Store - Search Books")
    searchForm = new SearchBooksForm(selenium)
  }
}

All fields from our form become properties in our form class, in this case, author and title.
We will use these properties to fill in the form.

class SearchBooksForm extends Form {

  private static final String AUTHOR_LOCATOR = "//form[@name='search_books']/input[@name='author']"
  private static final String TITLE_LOCATOR = "//form[@name='search_books']/input[@name='title']"

  String title
  String author

  SearchBooksForm(Selenium selenium) {
    super(selenium)
  }

  void fill() {
    selenium.typeKeys(AUTHOR_LOCATOR, author)
    selenium.typeKeys(TITLE_LOCATOR, title)
  }

  Button getSubmitButton() {
    return new Button(selenium, "//form[@name='search_books']/input[@type='submit']")
  }

  Page getPageAfterSubmit() {
    return new SearchResultPage(selenium)
  }

  String getAuthorFieldValue() {
    return selenium.getValue(AUTHOR_LOCATOR)
  }

  String getTitleFieldValue() {
    return selenium.getValue(TITLE_LOCATOR)
  }
}

Here is the SearchResultPage class:

class SearchResultPage extends Page {

  Form searchForm
  
  SearchResultPage(Selenium selenium) {
    super(selenium, "My Book Store - Search Result")
    this.searchForm = new SearchBooksForm(selenium)
  }
  
  def getResult() {
    def books = []
    def i = 1
    while (selenium.isElementPresent("//div[@id='books']/div[@class='book'][${i}]/span[@class='title']/text()")) {
      books << selenium.getText("//div[@id='books']/div[@class='book'][${i}]/span[@class='title']/text()")
      i++
    }
    return books
  }
}

so now we can finish our test code:

class MyBookStoreTests extends SeleneseTestCase {
  def testShouldFindTolkienBooks() {
    selenium.open HomePage.URL
    def homePage = new HomePage(selenium)
    def searchPage = page.navMenu.searchBooksLink.click()
    searchPage.searchForm.author = "Tolkien"
    searchPage.searchForm.title = "Rings"
    searchPage.searchForm.fill()
    def resultPage = searchPage.searchForm.submit()
  
    assertTrue resultPage.result.contains("The Lord Of The Rings: The Return Of The King")
    assertEquals "Tolkien" resultPage.searchForm.authorFieldValue
    assertEquals "Rings" resultPage.searchForm.titleFieldValue
  }
}

But this test code is still not very good.. it could be more readable and all these accesses like a.b.c.d() bum me out.
So let’s improve it a bit.

Improvements

First of all, let’s try to get rid of that selenium reference there inside of our test code.
To do that we can create our own extension of SeleneseTestCase with some utility methods and then make our test cases extend from it instead.

As we are using Groovy, we can create a method that creates the first page object our test needs for us, it can do it based on its class name and set its selenium dependency too. Let’s call this method visit, this way we can write on our test visit(HomePage).

class MyBookStoreSeleneseTestCase extends SeleneseTestCase {
  Page visit(Class clazz) {
    assert Page.isAssignableFrom(clazz)
    assert Page.URL
    selenium.open clazz.URL
    return clazz.newInstance(selenium)
  }
}

And our test becomes:

class MyBookStoreTests extends MyBookStoreSeleneseTestCase {
  def testShouldFindTolkienBooks() {
    def homePage = visit(HomePage)
    def searchPage = page.navMenu.searchBooksLink.click()
    searchPage.searchForm.author = "Tolkien"
    searchPage.searchForm.title = "Rings"
    searchPage.searchForm.fill()
    def resutPage = searchPage.searchForm.submit()
  
    assertTrue resultPage.result.contains("The Lord Of The Rings: The Return Of The King")
    assertEquals "Tolkien" resultPage.searchForm.authorFieldValue
    assertEquals "Rings" resultPage.searchForm.titleFieldValue
  }
}

Usually in spoken language the verbs come first, so we can improve our custom SeleneseTestCase class by adding some dummy methods just to make our test more readable, delegating the actions to our objects:

class MyBookStoreSeleneseTestCase extends SeleneseTestCase {
  Page visit(Class clazz) {
    assert Page.isAssignableFrom(clazz)
    assert Page.URL
    selenium.open "http://localhost:8080/my_book_store"
    return clazz.newInstance(selenium)
  }
  Page clickOn(Link link) {
    return link.click()
  }
  Page fillAndSubmit(Form form) {
    form.fill()
    return form.submit()
  }
}

class MyBookStoreTests extends MyBookStoreSeleneseTestCase {
  def testShouldFindTolkienBooks() {
    def homePage = visit(HomePage)
    def searchPage = clickOn(page.navMenu.searchBooksLink)
    searchPage.searchForm.author = "Tolkien"
    searchPage.searchForm.title = "Rings"
    def resutPage = fillAndSubmit(searchPage.searchForm)
  
    assertTrue resultPage.result.contains("The Lord Of The Rings: The Return Of The King")
    assertEquals "Tolkien" resultPage.searchForm.authorFieldValue
    assertEquals "Rings" resultPage.searchForm.titleFieldValue
  }
}

This test, as all other Selenium tests I have seen so far, only interacts with one page at a time. So we don’t need to have different variables for each page. We can have a single ‘page‘ variable and use it everywhere, something like this:

    ...
    def page = visit(HomePage)
    page = clickOn(page.navMenu.searchBookLink)
    ...

Then we can move the page variable to an attribute of MyBookStoreSeleneseTestCase and avoid having all those assignments:

class MyBookStoreSeleneseTestCase extends SeleneseTestCase {
  Page page
  Page visit(Class clazz) {
    assert Page.isAssignableFrom(clazz)
    assert Page.URL
    selenium.open clazz.URL
    page = clazz.newInstance(selenium)
    return page
  }
  Page clickOn(Link link) {
    page = link.click()
    return page
  }
  Page fillAndSubmit(Form form) {
    form.fill()
    page = form.submit()
    return page
  }
}

class MyBookStoreTests extends MyBookStoreSeleneseTestCase {
  def testShouldFindTolkienBooks() {
    visit HomePage
    clickOn page.navMenu.searchBooksLink
    page.searchForm.author = "Tolkien"
    page.searchForm.title = "Rings"
    fillAndSubmit page.searchForm
  
    assertTrue page.result.contains("The Lord Of The Rings: The Return Of The King")
    assertEquals "Tolkien" page.searchForm.authorFieldValue
    assertEquals "Rings" page.searchForm.titleFieldValue
  }
}

Then we can actually get rid of the references to ‘page‘ altogether from our test code. To do it we implement property and method missing in our MyBookStoreSeleneseTestCase, in a way that if we try to access a method or property that it doesn’t have, it looks for them in the page object:

class MyBookStoreSeleneseTestCase extends SeleneseTestCase {
  // all that we already had plus:
  def methodMissing(String method, args) {
    page.invokeMethod method, args
  }
  def propertyMissing(String name) {
    page[name]
  }
}

class MyBookStoreTests extends MyBookStoreSeleneseTestCase {
  def testShouldFindTolkienBooks() {
    visit HomePage
    clickOn navMenu.searchBooksLink
    searchForm.author = "Tolkien"
    searchForm.title = "Rings"
    fillAndSubmit searchForm
  
    assertTrue result.contains("The Lord Of The Rings: The Return Of The King")
    assertEquals "Tolkien" searchForm.authorFieldValue
    assertEquals "Rings" searchForm.titleFieldValue
  }
}

To finish, we can improve the filling out of the form using Groovy’s with method and use Hamcrest to write our assertions.

First we have to overwrite the method with in our Form class.
Groovy’s ‘with‘ method is very handy but it doesn’t return the own object. More about it here.
So we just add the following code to our Form class:

class Form {
  // ....
  def with(Closure c) {
    super.with(c)
    return this
  }
}

And then our test code can be written as:

class MyBookStoreTests extends MyBookStoreSeleneseTestCase {
  def testShouldFindTolkienBooks() {
    visit HomePage
    clickOn navMenu.searchBooksLink
    fillAndSubmit searchForm.with {
      author = "Tolkien"
      title = "Rings"
    }
  
    assertThat result, hasItem("The Lord Of The Rings: The Return Of The King")
    assertThat searchForm.authorFieldValue, is(equalTo("Tolkien"))
    assertThat searchForm.titleFieldValue, is(equalTo("Rings"))
  }
}

Much better uh?!

You can get the full code (with some workarounds to get it working without a real application) here.

About these ads
  1. Timur Strekalov
    September 30, 2010 at 10:13

    Halfway through reading the article I was going to shout: “but you could also create a method which would accept a page object instead of doing selenium.open HomePage.URL”, but I was patient and believed in you :) great article, although I’m not sure about the whole one-page-per-test thing, not always the case :( Hulk sad :'(

    It might be better to create and cache instances of the pages you visit and simply keep a reference to the active one. Consider doing something like:

    Page activePage

    def pageCache = [:]

    Page visit(Class clazz) {
    assert Page.isAssignableFrom(clazz)
    assert Page.URL
    selenium.open clazz.URL

    activePage = pageCache[clazz]

    if (!activePage) {
    activePage = clazz.newInstance(selenium)
    pageCache[clazz] = activePage
    }

    return activePage
    }

    And then, of course, change all references to page to reference “activePage” instead (I just want the name to be more obvious). This way you might use different pages in a test (which is often the case – you change a setting on the settings page, save and see if the result is what you want).

    But I may be just sleepy and you can as well disregard what I’m rambling about here :D

    BTW, take a look at the 23rd line of the 6th example from the end, you have a typo there :P yep, I’m just being pedantic, I know, but you know me, I can’t help it :D

    Anyhoo, superb, keep on rollin’ :)

    • September 30, 2010 at 17:32

      Hey!

      I am not sure I understood the page cache thingy. Without the cache you can still navigate through different pages. Unless you design your page objects to be stateful, which I am not sure is a good idea.

      Using your example, if you change the settings in page A then go to page B to check if something has changed, page B’s object might be a completely new object, the checks would be done using Selenium’s API, not your page objects’ properties. Otherwise you’ll be testing your test framework code, not your application :P

      Thanks for catching that typo, though! :D
      I forgive you for being pedantic, this is part of what makes you such a character hahaha

      Thanks for the feedback and keep on rollin’ you there, German boy! Gesundheit!

      • Timur Strekalov
        September 30, 2010 at 17:45

        oh shit, I started out with a totally different concept (I was thinking in terms of JS, to assign pages to different properties so you could actually see from the test which page you are using), then I changed the thing halfway through and screwed up in the end :D

        told you, I was sleepy! :)))

        My point was that you could access pages with something like myPage rather than page, but for that you’d change the propertyMissing method I guess.

        No need to cache anything, as I said, disregard that. :P

  2. Scott
    January 22, 2011 at 20:13

    I’m a Sel beginner – just transitioning from IDE > RC.
    What a great, well formed example. Nice work Luiz!

  3. Anonymous
    June 21, 2011 at 03:29

    this is a great article for rookies

  4. August 15, 2011 at 21:16

    I feel it is a little complicated, especially you added a form object. It’s kind of redundant.

    • September 2, 2011 at 07:59

      I think it really depends on the application you are testing. If it’s a simple application with few pages, then I believe this level of abstraction for our page objects may indeed be too much.
      For more complex applications with many pages containing many different widgets I think this approach may be really beneficial.

  5. Vadim
    December 23, 2012 at 00:21

    Really cool article man! just sarted learning automation and thanx god i’ve used your article as a first source of learning!

  6. March 27, 2014 at 16:54

    My wife had arrived from the South, so I was in good hands.
    two o’clock in the Gosh-darned morning–way past my beddy-time–and you all ain’t
    here to listen to me gas. About how candy crush saga cheats snuck up on most of
    us.

  7. June 2, 2014 at 10:50

    I know this site offers quality dependent articles or
    reviews and other material, is there any other website
    which gives such information in quality?

  1. October 4, 2010 at 15:17

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: