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