Sep 12, 2013

Type Safe Page Objects with Geb

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:

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:

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:

With the corresponding page objects being:

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:

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:

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:

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!

About the Author

Object Partners profile.

One thought on “Type Safe Page Objects with Geb

  1. Roberto Guerra says:

    Do you have an example of a geb setup with gradle? I’ve been using casperjs but want to try out geb. I tried running the sample project in github but it throws an exception https://github.com/geb/geb-example-gradle.

    1. Craig Atkinson says:

      Hi Roberto,

      I’m sorry you ran into problems with the geb-example-gradle project. (Was this defect from you? https://github.com/geb/geb-example-gradle/issues/8) That project runs tests in Firefox and Chrome by default. To run tests in Chrome, Selenium requires an OS-specific ChromeDriver library to be installed on your local machine. There are instructions on how to install ChromeDriver here: https://code.google.com/p/selenium/wiki/ChromeDriver

      I’ll also submit a pull request to the geb-example-gradle project to include info about ChromeDriver in the project readme.

      Or if you don’t want to install ChromeDriver, you can run the tests just in Firefox, which doesn’t require any extra libraries. To run the tests only in Firefox, run ‘./gradlew firefoxTest’

      Craig

  2. LL says:

    Absolutely agree, that page objects helps to create more readable tests. I’ve moved in my tests all related actions into page objects. There are some complicated actions like fillForm(dataset) or verifyForm() etc
    And it’s really very convenient, because in case of some changes in form you doesn’t need to change all specs, but just correct one method in page object.

  3. Jordan says:

    Excellent post! After discovering geb a couple days ago I just reached the point where I was thinking about these exact same concerns and this approach makes perfect sense. I haven’t had this much fun writing tests since…well…ever!

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Blog Posts
Android Development for iOS Developers
Android development has greatly improved since the early days. Maybe you tried it out when Android development was done in Eclipse, emulators were slow and buggy, and Java was the required language. Things have changed […]
Add a custom object to your Liquibase diff
Adding a custom object to your liquibase diff is a pretty simple two step process. Create an implementation of DatabaseObject Create an implementation of SnapshotGenerator In my case I wanted to add tracking of Stored […]
Keeping Secrets Out of Terraform State
There are many instances where you will want to create resources via Terraform with secrets that you just don’t want anyone to see. These could be IAM credentials, certificates, RDS DB credentials, etc. One problem […]
Validating Terraform Plans using Open Policy Agent
When developing infrastructure as code using terraform, it can be difficult to test and validate changes without executing the code against a real environment. The feedback loop between writing a line of code and understanding […]