Type Safe Page Objects with Geb

How to get the benefits of type-safe page objects with the Geb functional testing framework.

Object Partners

Geb is a popular browser functional testing library written in Groovy and based on Selenium 2. Geb ships with some wonderful additions to the Selenium library, including first-class support for page objects. Geb also has a very extensive and up-to-date manual.

Geb page objects provide a great way to keep the markup-specific parts of a page out of the test itself. In essence page objects are a level of abstraction from the page itself.

Why use page objects? Why not just use Geb selectors in the test itself?

Here’s what a simple test without page objects might look like:

go "home/index"

$("#login_link").click()

$("#username").value("user1")
$("#password").value("password1")
$("#login_button).click()

You’ll notice that HTML-specific information (the element IDs) are sprinkled throughout the test. In this case it’s fairly obvious what the elements are just based on their ID. But what if it’s less obvious? It could be tricky to decipher exactly what the test is trying to do. For example, this simple test searches for a string on Google:

go "http://www.google.com"

$("input", name: "q").value("Grails")
$("button", name: "btnG").click()

waitFor { $("div", id: "search").displayed }

assert $("div", id: "search").text().contains("grails.org")

We can guess that the ‘input’ element named ‘q’ is the search box and the button ‘btnG’ is the search button, but it’s cryptic. What if instead we moved all that markup-specific information into a central place and referred to more descriptive names in the test? That’s the essence of what page objects are.

A test with using page objects might look something like this:

to HomePage
loginLink.click()

username = "user1"
password = "password1"
loginButton.click()

With the corresponding page objects being:

class HomePage extends geb.Page {
  static content = {
    loginLink(to: LoginPage) { $("#login_link") }
  }
}

class LoginPage extends geb.Page {
  static content = {
    username { $("#username") }
    password { $("#password") }
    loginButton(to: DashboardPage) { $("#login_button") }  
  }
}

These page objects are pretty much the usual page objects in Geb. They contain the selectors needed to interact with the elements on the page.

The page objects are a great step up from the non-page-object test, but they could still be improved in a couple ways:

  1. The test is still a little more verbose than it needs to be. We have to explicitly set each input field and click each link and button. What if we had methods that encapsulated all the necessary operations to perform one of the actions, such as logging in?

  2. Under the covers Geb keeps track of the current page, but it’s not obvious from the test what the current page is at any given point and what actions are available on that page. Knowing the current page becomes especially helpful when the application being tested is more complex with many different application flows and branches. What if we had an easy way to know what the current page is and what actions are available on that page?

One solution that can help with these issues is an additional layer of abstraction in the page object. We can use the page object to hold the code required for all the actions that are available on the page. And similar to a builder pattern, we can return to the test an instance of the current page from each action method that changes the page.

A test with the page object builder might look like:

HomePage homePage = to(HomePage)

LoginPage loginPage = homePage.clickLoginLink()

loginPage.login("user1", "password1")

In Geb v0.9.0+, the ‘to(PageClass)’ method returns the instance of the page, making it a little easier to work a page object builder (previously the test would have to call ‘browser.page’ to get the instance of the page).

And the corresponding page objects contain action methods for the actions we take on the page and return a page object instance representing the new page when changing pages:

class HomePage extends geb.Page {
  static content = {
    loginLink(to: LoginPage) { $("#login_link") }
  }

  LoginPage clickLoginLink() {
    loginLink.click()

    return browser.page
  }
}

class LoginPage extends geb.Page {
  static content = {
    usernameField { $("#username") }
    passwordField { $("#password") }
    loginButton(to: DashboardPage) { $("#login_button") }  
  }

  DashboardPage login(String username, String password) {
    usernameField.value(username)  
    passwordField.value(password)
    loginButton.click()

    return browser.page
  }
}

Since each we have a typed reference to the current page and the page class has normal methods on it representing the available actions, we also get convenient code completion when writing the tests.

There are also ways to make the test even more concise. This test isn’t interested in the home page other than using it to get to the login page. And even the login page is just a gateway to get to the dashboard page. Since each action is returning an instance of the page, we can chain calls together to create more succinct test test code that resembles a builder:

DashboardPage dashboardPage = to(HomePage).clickLoginLink().login('user1', 'password1')

Page objects are a great way to create readable, maintainable tests. And we can go one step further in those efforts using a page object builder pattern. I hope these techniques prove useful to you in your testing efforts. For more information on Geb, check out the Geb manual or the slides from my recent Gr8Conf presentation on Geb

Update: After I wrote the first draft of this post, my pull request to add info about the page object builder pattern to the Geb manual was accepted - so look for this info in the upcoming releases of the Geb manual!

Share this Post

Related Blog Posts

JVM

Optional Typing in Groovy

August 19th, 2013

Discussion of Groovys Optional Typing system with benefits and drawbacks outlined.

Object Partners
JVM

Customizing MOP in Groovy

July 30th, 2013

Demonstrates using the Groovy Meta Object Protocol framework to build a custom MetaClass implementation.

Object Partners
JVM

Creating self-contained, executable Jars with Gradle and Shadow

July 16th, 2013

Describes how to use Gradle and its Shadow plugin to create a self contained, executable Jar. No more classpath management.

Object Partners

About the author