Making Web Tests Readable, Robust and Rapid

Tutorial: Groovy Functional Testing with Geb - Part 4

   

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

   

Pages

Ellery Crane

What do you think?

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

Comments

Latest opinions