Making Web Tests Readable, Robust and Rapid

Tutorial: Groovy Functional Testing with Geb - Part 3

    

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.

    

Pages

Ellery Crane

What do you think?

JAX Magazine - 2014 - 05 Exclucively for iPad users JAX Magazine on Android

Comments

Latest opinions