JAX London 2014: A retrospective
Making Web Tests Readable, Robust and Rapid

Tutorial: Groovy Functional Testing with Geb

ElleryCrane
geb

Ellery Crane explores Groovy browser automation solution Geb and how it can be used to write easy and top-notch functional tests.

Functional testing web applications effectively has always been a challenge. Tests are expensive to write, painful to read, and devilishly hard to maintain. Using Geb, a powerful Groovy browser automation tool, writing first-rate functional tests has never been easier.

I have been developing web applications my entire career. Since the beginning, I’ve held automated testing to be one of the pinnacles of software development best practices. Unit testing in particular has been part of my development process since I was an undergraduate. One of the challenges I’ve experienced when developing for the web is that traditional unit testing only goes so far. Some of the most serious bugs in a web application tend to be things a unit test has no way to check for; a JavaScript error in a particular version of a specific browser, or perhaps an API change in a third party RESTful service that your application is consuming. Detecting these kinds of regressions requires testing on a wholly different level.

Enter functional testing. Unlike unit testing, functional testing ignores the specifics of the underlying software component under test. A functional test merely asserts that providing certain input results in certain output. In the context of a web application, this usually means programmatically controlling a web browser to simulate the actions of a user on a web page. For example, a functional test might verify that navigating to a search engine page, inputting a particular search term in the search box, and then clicking the submit button takes you to a search results page that displays results with the selected term. The underlying application logic that actually performs the search is irrelevant to the test as long as the expected results show up.

This type of test can be exceptionally valuable if done correctly. If development on any component involved in the function being tested introduces a regression, the test will fail. It doesn’t matter whether the component lies in the front end (JavaScript or markup, primarily) or the back end—all of the application layers are being tested at the same time.

Unfortunately, tools for writing and executing functional tests have, historically, been notoriously cumbersome to use. Browser and environmental differences make it a challenge to run tests by developers on different computers, if they can be run at all. The intricacies of selecting and manipulating data in the DOM has also meant that, even after investing the time and effort into the writing of functional tests, they tend to be extremely brittle and hard for other developers to understand. As such, though I always yearned for my web applications to have a proper suite of automated functional tests to rely upon, I learned to do without.

Geb changed all of that. Not as clumsy or fragile as other browser automation frameworks, Geb is an elegant tool for a more civilized age. Geb’s Page Objects and Groovy DSL make tests readable to the point that they’re almost plain English. The encapsulation of content definition inside of those Page Objects also reduces test fragility by letting you reason about your web pages and components as though they were part of an object hierarchy. If the structure of a page changes, you need only update a selector in one place within your Page class—often, no changes to the test code itself are required. What’s more, Geb lets you define content using a powerful selector syntax familiar to anyone acquainted with jQuery.

In this article, I will provide an introduction to Geb and an overview of its use as a tool for functional testing. I will then present an example of Geb testing in action, and show how it can be used to mitigate problems in areas that other functional testing frameworks fall short on.

          

What is Geb?

Despite the focus of this article, it is important to establish straight away that Geb (pronounced “jeb”) is a browser automation tool, not a testing tool. Geb can be used to programmatically automate a web browser for any purpose whatsoever; it just so happens that web testing is the most common need the average programmer has for browser automation.

Geb is built on top of the WebDriver browser automation library, which is the successor to the popular Selenium Remote Control (RC) testing framework. Unlike the original Selenium RC, which uses JavaScript to interact with page content and control the browser, WebDriver utilizes native browser drivers to perform its automation. This is an important distinction for purposes of web testing—using Geb and WebDriver gives you the assurance that your application is behaving correctly in the browsers you test it on. It also provides access to browser commands that are simply inaccessible without native driver support.

What sets Geb apart from just using WebDriver on its own? From Geb’s home page: “It brings together the power of WebDriver, the elegance of jQuery content selection, the robustness of Page Object modeling and the expressiveness of the Groovy language.” Furthermore, Geb provides first class support and integration with common testing frameworks and build tools, including JUnit, Spock, Grails, Maven and Gradle.

It should be noted that Geb’s user manual is top notch; any developer looking for more examples, detailed API documentation or setup guides would be well advised to check it out.

          

Writing a Geb-Powered Functional Test

To get a sense of the power Geb brings to functional testing, let us consider a simple example application that consists only of a login page and user home page. The login page has a form that allows a user to enter their username and password. A successful form submission from the login page will log the user in and take them to the user home page. An unsuccessful form submission will redisplay the login page with an error message. Using Geb, we will write functional tests for both of these behaviors.

Listing 1 – login.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

"http://www.w3.org/TR/html4/loose.dtd">

<html>

<head><title>Login Page</title></head>

<body>

<h1 class="page-header">Login</h1>

<ul class="errors"> <!-- Only included if there were submission errors -->

<!-- Any submission errors will be displayed as list item elements here -->

</ul>

<form id="login-form" action="checkLogin.html" method="post">

<label for="username-field">Username</label>

<input type="text" name="username" id="username-field" />

<br />

<label for="password-field">Password</label>

<input type="password" name="password" id="password-field" />

<br />

<input type="submit" value="Submit" />

</form>

</body>

</html>

Listing 1 contains the markup of our login page. While a functional test for this page could be written using any testing framework that supports Groovy, Geb has built in integrations with several of the most popular, including Spock, JUnit, TestNG, EasyB and Cuke4Duke. I will be using Spock for this example, as its highly human-readable specification language dovetails perfectly with Geb’s own Groovy DSL. We will begin with a simple test that bears resemblance to many traditional (and fragile) functional tests as an introduction to Geb’s Browser object and jQuery-ish Navigator API, and then revisit it later to demonstrate how using Geb’s Page Object pattern can reduce fragility and enhance readability.

Listing 2 – SimpleLoginSpec.groovy

 

import geb.spock.GebSpec
class SimpleLoginSpec extends GebSpec{
    def "should login with valid username and password"(){
        when:
        go "login.html"

        then:
        $(".page-header").text() == "Login"

        when:
        $("#login-form input[name=username]").value("user1@example.com")
        $("#login-form input[name=password]").value("goodpassword")
        $("#login-form input[type=submit]").click()

        then:
        $(".page-header").text() == "User Home Page"
    }

    def "should redisplay form with an error message when password is bad"(){
        when:
        go "login.html"

        then:
        $(".page-header").text() == "Login"

        when:
        $("#login-form input[name=username]").value("user1@example.com")
        $("#login-form input[name=password]").value("badpassword")
        $("#login-form input[type=submit]").click()

        then:
        $(".page-header").text() == "Login"
        $(".errors li").size() == 1
        $(".errors li")[0].text() == "Invalid username or password"

 

Listing 2 contains the source code for our simple test class. Let’s begin by going over what’s happening. Our test class, SimpleLoginSpec, extends GebSpec, which is a Geb-furnished extension to Spock’s Specification class. For those unfamiliar with the anatomy of a Spock specification, it should be explained that each method defines a test case. Using a language feature of Groovy, the method names can be defined as strings to provide expressive, human readable descriptions of what each test is doing. Each method contains “when” and “then” blocks — the code in “when” blocks sets up data and performs actions, while each line in a “then” block is an implicit assert statement. If any line in a “then” block returns false (as defined using Groovy Truth), the test fails.

The first test case verifies that submitting a valid username and password takes the user to the user home page. The very first step in testing this behavior is navigating to the login page. We do this by using GebSpec’s go() method. Under the hood, this is actually a call to the go() method on an instance of Geb’s Browser class, which itself wraps an instance of WebDriver. GebSpec makes use of Groovy’s methodMissing() and propertyMissing() methods to forward method calls to the Browser object, reducing the noise in the test. The go() method drives the browser to the URL specified; in this case, since the URL is not an absolute path, it will be appended to the value of Geb’s baseUrl property. You can set the baseUrl either using a system property or by making use of Geb’s excellent Config Script.

After the browser has navigated to the page we specified in the “when” block, the first “then” block checks that the page we’re at is actually our login page. This is also our first taste of Geb’s “jQuery-ish” Navigator API, which is “a jQuery inspired mechanism for finding, filtering and interacting with DOM elements”. The $() method, like the go() method, is being delegated to the Browser object. The arguments to this method can be a combination of CSS selectors, indexes, and attribute/text matchers. Invoking the $() method returns one or more of Geb’s Navigator objects. Navigator objects provide access to the data contained in the matched content, as well as methods to allow additional filtering and matching. In this case, to verify that we are at the login page, we use the $() method to select the element on the page with the CSS class “page-header”, and then verify that the text in that element matches our that expected on our login page.

The second “when” block in our test uses the navigator API to select the username and password form inputs, and then uses the Navigator object’s value() method to set their values to those of the username and password we’re testing. If you want to read the value of a form input instead of write to it, calling value() without arguments will return the input’s current value. After filling in values for our username and password, we finish by selecting the submit button and instructing the browser to click on it using the click() method. Finally, the last “then” block simply verifies that the content of page header is now “User Home Page”, indicating that we have logged in successfully.

The second test case is very similar to the first, save that we are setting the value of the password input to a password we expect to be invalid. Rather than test that we have reached the user home page, the final “then” block confirms that we are still on the login page and that an error is displayed. The selection of the errors content demonstrates an important feature of the navigator API—namely, that in the case where the selector matches multiple elements, we can treat it as a collection and make use of all of the regular Groovy collection methods.

While our SimpleLoginSpec tests our login page’s functionality, it still leaves much to be desired. The test code is a mess of selectors and hard coded text values. In this case, most of the selectors are understandable semantically, but this is hardly something that can be guaranteed in every test we might write. Indeed, as applications grow in complexity, the selectors necessary to precisely identify content often become far too convoluted to be human readable. Even were that not the case, embedding the selectors and expected values directly in the test code like this makes the test extremely fragile. If, for example, the id of the login form element were to change in the markup, both test cases would fail regardless of whether or not the login form still worked successfully. Furthermore, fixing such a test failure would require changes to almost every line of test code! Thankfully, Geb provides a solution.

        

Understanding the Page Object Pattern

Geb’s implementation of the Page Object Pattern is one of its more compelling features from the perspective of a functional tester.

Page Objects can most easily be thought of as an object hierarchy modeling the pages in a web application. Most web applications can be conceptually broken into different pages very easily; most applications have pages a ‘login page’, a ‘home page’, a ‘user information page’ and so on. Using Geb, we would represent these pages with unique classes that extend Geb’s Page class. Each Page class implementation defines the content and functionality present on the web page it is modeling. From a semantic standpoint, the specific nature of the page content is irrelevant; it doesn’t matter whether the ‘submitButton’ content is actually a button, an input element of type ‘submit’, or even a hyperlink. Externally, all that matters when using the Page object is that there is a ‘submitButton’ that can be accessed and interacted with.

The Page class not only serves as a place to define what content is on the page, but how that content is represented in the DOM. In the simple case, each piece of content defined in the Page class is mapped to a selector that is used to actually retrieve that content from the web page. In this way, the implementation details of the content are encapsulated entirely within the Page class and hidden from the outside world.

Listing 3 – LoginPage and UserHomePage

 

import geb.Page

class LoginPage extends Page{

static url = "login.html"

static at = { header.text() == "Login" }

static content = {

header { $(".page-header") }

loginForm { $("#login-form") }

username { loginForm.username }

password { loginForm.password }

submitButton(to: UserHomePage) {

loginForm.find("input", type: "submit")

}

errors(required:false) { $(".errors li") }

invalidUsernameOrPasswordError(required:false) {

errors.filter(text:contains("Invalid username or password"))

}

}

}

class UserHomePage extends Page{

static url = "userHome.html"

static at = { header.text() == "User Home Page" }

static content = {

header { $(".page-header") }

}

}

 

Listing 3 contains the source code to Page object representations of our login page and user home page. After examining how everything is wired together, we will revisit our original functional test using these Page objects to drive our test code.

Every Page subclass can define several static properties that describe the page’s attributes and behavior. The url property defines the actual URL to associate with this page, and is used by the Browser object to navigate to the page when you invoke the to() method. As before, we can define relative URLs that are used in conjunction with the baseUrl config property to construct the full URL. The at property is a closure that is called whenever the Page’s verifyAt() method is invoked, and should return a boolean value indicating whether the browser is actually at the page or not (though it’s often actually smarter to place an explicit assert statement in the closure rather than returning true—this allows you to levy Groovy’s power assert feature to get more information about a test failure). The content static property is a closure that defines content on the page using Geb’s content DSL. The basic structure to this DSL is:

 

«name» { «definition» }

or:

«name»(«options map») { «definition» }

 

Each named piece of content is followed by a closure that returns whatever data is associated with that content. Optionally, you can provide a map of options that are used to aid Geb in interacting with the content appropriately. The submitButton in our LoginPage, for instance, specifies that it has a “to” value of UserHomePage. This tells Geb that clicking on that content can result in navigating to the specified Page so that you do not have to explicitly tell the Browser that the page has changed. The various errors content all specify a “required” value of false. This tells Geb that the content will not always be on the page. If you fail to specify that a particular piece of content is not required, a RequiredPageContentNotPresentException will be thrown when that content is accessed.

Content defined in the content closure can be accessed as though it were a property on the Page class itself. It can even be referenced by other content definitions or from within the at closure, such as how the username, password, and submitButton content all reference the loginForm content and the at closure references the header content. The username and password content definitions also illustrate Geb’s “form control shortcuts” feature: a Navigator instance referencing a form element treats the form’s inputs as implicit properties on the Navigator object with the same names as their “name” attribute. Hence, the following two selectors are identical:

 

$("form").find("input", name:"username")

$("form").username

      

 

Revisiting our Functional Test

With our login page and user home page defined, let us revisit our previous functional test. Listing 4 contains a rewritten LoginSpec that utilizes our LoginPage and UserHomePage objects.

Listing 4 – LoginSpec.groovy

 

import geb.spock.GebSpec

class LoginSpec extends GebSpec{

def "should login with valid username and password"(){

when:

to LoginPage



then:

at LoginPage



when:

username = "user1@example.com"

password = "goodpassword"

submitButton.click()



then:

at UserHomePage

}



def "should redisplay form with an error message when password is bad"(){

when:

to LoginPage



then:

at LoginPage



when:

username = "user1@example.com"

password = "badpassword"

submitButton.click()



then:

at LoginPage

errors.size() == 1

invalidUsernameOrPasswordError.present

}

}

 

There are a few differences between SimpleLoginSpec and LoginSpec that merit explanation. First note that, rather than use the go() method to navigate to our login page, the new test makes use of the Browser’s to() method. The to() method takes a Page class as its argument, and uses the page’s static url property to determine where to navigate to. Additionally, invoking the to() method tells the Browser that it should treat the specified page as the current page. Invoking the Browser’s at() method calls the at closure defined on the page object to determine whether navigating to the page was successful.

As the GebSpec delegates method and property calls to the Browser object, so too does the Browser delegate method and property calls to the current Page object. Because our first “when”/”then” blocks set the current page to LoginPage, we can access its content as though it is defined in the test class itself. The second “when” block accesses the LoginPage’s username and password content, using Geb form control shortcuts to set their values as though they are properties (the use of this syntax is identical to calling the value() method on the content). We then instruct the browser to click on the page’s submitButton content before verifying the page has been successfully changed. Recall that because we specified the UserHomePage as a value for the “to” option in the submitButton’s options map, the Browser knows that clicking on the content should take us to that page without us needing to state it explicitly.

In the second test case, we manually verify that the optional errors content is present after submitting the form containing the bad password. Every Navigator instance has a present property that can be used to check its existence on the page, which we invoke manually here to verify the specific error we are expecting is actually there.

        

Comparing the Tests

Consider the differences between SimpleLoginSpec and LoginSpec. By using page objects and content definition in LoginSpec, we were able to test the same functionality as SimpleLoginSpec in an incredibly more readable and robust fashion. LoginSpec’s test code almost reads as a plain English description of what the test is doing and expecting. SimpleLoginSpec may test the same behavior, but making sense of the raw selectors is at best difficult and at worst can lead to heaps of wasted development time spent simply trying to figure out what the test is attempting to do. Using a page object model to expose the semantics of the page rather than the implementation details results in tests that can actually serve as application documentation. In many cases, such tests can be more valuable as clear documentation of expected application behavior than they are as regression tests!

I discussed earlier how selectors in SimpleLoginSpec were extremely sensitive to change, resulting in brittle tests that require large amounts of refactoring to correct failures caused by minor disturbances to the markup. Consider the impact of changing the id of the login form from “login-form” to “user-sign-in-form”. LoginSpec would fail, just as SimpleLoginSpec would. Unlike SimpleLoginSpec, however, LoginSpec can be fixed without changing a single line of test code. In fact, the only change needed would be the line in LoginPage that defines the loginForm content:

loginForm { $(“#user-sign-in-form”) }

Because all of the other content definitions reference loginForm rather than include the form’s id explicitly, no other changes are needed. Certainly, there may be some breaking markup changes that can be a pain to refactor. The page object model, however, ensures that most of that work is encapsulated entirely within the Page class and not within the test code.

The functional tests I’ve presented here only scratch the surface of the gains that are possible using Geb. A well-defined, extensible page object model contributes more than just robust and readable functional tests. Other developers can write their own tests more quickly and concisely by using existing Page classes. Refactoring commonly used page content into Module classes that can be reused across multiple Pages can reduce code duplication. Furthermore, Page objects are still traditional GroovyObjects and can make use of all of the standard object oriented features—inheritance, method definition and so on—to become extremely powerful and reusable testing components. When you start to think of modelling your web pages in the same way you would any other data, your functional tests can reap the same object-oriented programming benefits that you’re used to in your application code.

Conclusions

Any serious web application developer should heed the benefits of automated functional testing. Geb is the most elegant and powerful browser automation tool available today. Proper use of Geb’s Page Object implementation can yield extremely readable and robust functional tests that can both detect regressions and serve as documentation of application behavior. Geb’s expressive content definition DSL and jQuery-ish Navigator API make writing such tests a breeze. If you’re interested in getting started with Geb yourself (and how could you not be?), The Book Of Geb contains all of the information you could hope for and more.

If you still get stuck, fear not—the Geb mailing list is extremely active, responsive and friendly. You owe it to yourself to explore everything Geb has to offer, because it’s the tool your web application deserves. With Geb, writing clear, powerful and maintainable functional tests has never been easier.

Author
Comments
comments powered by Disqus