The Book Of Geb

— Very Groovy browser automation… web testing, screen scraping and more —

Version: 0.9.2

1 Introduction

Geb is a developer focused tool for automating the interaction between web browsers and web content. It uses the dynamic language features of Groovy to provide a powerful content definition DSL (for modelling content for reuse) and key concepts from jQuery to provide a powerful content inspection and traveral API (for finding and interacting with content).

Geb was born out of a desire to make browser automation (originally for web testing) easier and more productive. It aims to be a developer tool in that it allows and encourages the using of programming and language constructs instead of creating a restricted environment. It uses Groovy’s dynamism to remove the noise and boiler plate code in order to focus on what’s important — the content and interaction.

1.1 The Browser Automation Technology

Geb builds on the WebDriver browser automation library, which means that Geb can work with any browser that WebDriver can. While Geb provides an extra layer of convenience and productivity, it is always possible to “drop down” to the WebDriver level to do something directly should you need to.

For more information see the manual section on using a driver implementation.

1.2 The Page Object Pattern

The Page Object Pattern gives us a common sense way to model content in a reusable and maintainable way. From the WebDriver wiki page on the Page Object Pattern:

Within your web app’s UI there are areas that your tests interact with. A Page Object simply models these as objects within the test code. This reduces the amount of duplicated code and means that if the UI changes, the fix need only be applied in one place.

Furthermore (from the same document):

PageObjects can be thought of as facing in two directions simultaneously. Facing towards the developer of a test, they represent the services offered by a particular page. Facing away from the developer, they should be the only thing that has a deep knowledge of the structure of the HTML of a page (or part of a page) It’s simplest to think of the methods on a Page Object as offering the “services” that a page offers rather than exposing the details and mechanics of the page. As an example, think of the inbox of any web-based email system. Amongst the services that it offers are typically the ability to compose a new email, to choose to read a single email, and to list the subject lines of the emails in the inbox. How these are implemented shouldn’t matter to the test.

The Page Object Pattern is an important technique, and Geb provides first class support via its page and module constructs.

1.3 The jQuery-ish Navigator API

The jQuery JavaScript library provides an excellent API for (among other things) selecting or targeting content on a page and traversing through and around content. Geb takes a lot of inspiration from this.

In Geb, content is selected through the $ function, which returns a Navigator object. A Navigator object is in someways analogous to the jQuery data type in jQuery in that it represents one or more targeted elements on the page.

Let’s see some examples:

// match all 'div' elements on the page
$("div")

// match the first 'div' element on the page
$("div", 0)

// match all 'div' elements with a title attribute value of 'section'
$("div", title: "section")

// match the first 'div' element with a title attribute value of 'section'
$("div", 0, title: "section")

// match all 'div' elements who have the class 'main'
$("div.main") 

// match the first 'div' element with the class 'main'
$("div.main", 0) 

These methods return Navigator objects that can be used to further refine the content.

// The parent of the first paragraph
$("p", 0).parent()

// All tables with a cellspacing attribute value of 0 that are nested in a paragraph
$("p").find("table", cellspacing: '0')

This is just the beginning of what is possible with the Navigator API. See the chapter on the navigator for more details.

1.4 Full Examples

Let’s have a look at a simple case of wanting to search for “wikipedia” on Google and follow the first result returned.

1.4.1 Inline Scripting

Here’s an example of using Geb in an inline (i.e. no page objects or predefined content) scripting style…

import geb.Browser

Browser.drive {
    go "http://google.com/ncr"

    // make sure we actually got to the page
    assert title == "Google"

    // enter wikipedia into the search field
    $("input", name: "q").value("wikipedia")

    // wait for the change to results page to happen
    // (google updates the page dynamically without a new request)
    waitFor { title.endsWith("Google Search") }

    // is the first link to wikipedia?
    def firstLink = $("li.g", 0).find("a.l")
    assert firstLink.text() == "Wikipedia"

    // click the link 
    firstLink.click()

    // wait for Google's javascript to redirect to Wikipedia
    waitFor { title == "Wikipedia" }
}

1.4.2 Scripting with Page Objects

This time let us define our content up front using the Page Object pattern…

import geb.Browser
import geb.Page
import geb.Module

// modules are reusable fragments that can be used across pages that can be paramaterised
// here we are using a module to model the search function on the home and results pages
class GoogleSearchModule extends Module {

    // a paramaterised value set when the module is included
    def buttonValue

    // the content DSL
    static content = {

        // name the search input control “field”, defining it with the jQuery like navigator
        field { $("input", name: "q") }

        // the search button declares that it takes us to the results page, and uses the 
        // parameterised buttonValue to define itself
        button(to: GoogleResultsPage) { 
            $("input", value: buttonValue)
        }
    }
}

class GoogleHomePage extends Page {

    // pages can define their location, either absolutely or relative to a base
    static url = "http://google.com/ncr"

    // “at checkers” allow verifying that the browser is at the expected page
    static at = { title == "Google" }

    static content = {
        // include the previously defined module
        search { module GoogleSearchModule, buttonValue: "Google Search" }
    }
}

class GoogleResultsPage extends Page {
    static at = { title.endsWith "Google Search" }
    static content = {
        // reuse our previously defined module
        search { module GoogleSearchModule, buttonValue: "Search" }

        // content definitions can compose and build from other definitions
        results { $("li.g") }
        result { i -> results[i] }
        resultLink { i -> result(i).find("a.l") }
        firstResultLink { resultLink(0) }
    }
}

class WikipediaPage extends Page {
    static at = { title == "Wikipedia" }
}

Now our script again, using the above defined content…

Browser.drive {
    to GoogleHomePage
    assert at(GoogleHomePage)
    search.field.value("wikipedia")
    waitFor { at GoogleResultsPage }
    assert firstResultLink.text() == "Wikipedia"
    firstResultLink.click()
    waitFor { at WikipediaPage }
}

1.4.3 Testing

Geb itself does not include any kind of testing or execution framework. Rather, it works with existing popular tools like Spock, JUnit, TestNG, Cucumber and EasyB. While Geb works well with all of these test tools, we encourage the use of Spock as it’s a great match for Geb with its focus and style.

Here is our Google case again, this time use Geb’s Spock integration…

import geb.spock.GebSpec

class GoogleWikipediaSpec extends GebSpec {

    def "first result for wikipedia search should be wikipedia"() {
        given:
        to GoogleHomePage

        expect:
        at GoogleHomePage

        when:
        search.field.value("wikipedia")

        then:
        waitFor { at GoogleResultsPage }

        and:
        firstResultLink.text() == "Wikipedia"

        when:
        firstResultLink.click()

        then:
        waitFor { at WikipediaPage }
    }
}

For more information on using Geb for web and functional testing, see the testing chapter.

1.5 Installation & Usage

Geb itself is a available as a single geb-core jar from the central Maven repository. To get up and running you simply need this jar, a WebDriver driver implementation and the selenium-support jar.

Via @Grab

@Grapes([
    @Grab("org.gebish:geb-core:0.9.2"),
    @Grab("org.seleniumhq.selenium:selenium-firefox-driver:2.26.0"),
    @Grab("org.seleniumhq.selenium:selenium-support:2.26.0")
])
import geb.Browser

Via Maven…

<dependency>
  <groupId>org.gebish</groupId>
  <artifactId>geb-core</artifactId>
  <version>0.9.2</version>
</dependency>
<dependency>
  <groupId>org.seleniumhq.selenium</groupId>
  <artifactId>selenium-firefox-driver</artifactId>
  <version>2.26.0</version>
</dependency>
<dependency>
  <groupId>org.seleniumhq.selenium</groupId>
  <artifactId>selenium-support</artifactId>
  <version>2.26.0</version>
</dependency>

Via Gradle…

compile "org.gebish:geb-core:0.9.2", "org.seleniumhq.selenium:selenium-firefox-driver:2.26.0", "org.seleniumhq.selenium:selenium-support:2.26.0"

Alternatively, if using an integration such as geb-spock or geb-junit you can depend on that instead of geb-core.

Be sure to check the chapter on build integrations for information on using Geb with particular environments
, like Grails.

2 The Browser

The entry point to Geb is the Browser object.
A browser object marries a WebDriver instance (which drives the actual web browser being automated) with the concept of a “current page”.

Browser objects are created with a configuration that specifies which driver implemenation to use, the base url to resolve relative links against and other bits of config. The configuration mechansism allows you to externalise how Geb should operate, which means you can use the same suite of Geb code or tests with different browsers or site instances. The chapter on configuration contains more details on how to manage the configuration parameters and what they are.

The default constructor of Browser simply loads its settings from the config mechanism.

import geb.Browser

def browser = new Browser()

However, if you prefer to specify the driver implementation (or any other settable property on the browser) you can by using Groovy’s map constructor syntax.

import geb.Browser
import org.openqa.selenium.firefox.FirefoxDriver

def browser = new Browser(driver: new FirefoxDriver())

Which is that same as…

def browser = new Browser()
browser.driver = new FirefoxDriver()

Any property set this way will override any settings coming from the config mechanism.

Note: The behaviour is undefined if a browser’s driver is changed after its first use so you should avoid setting the driver this way and prefer the configuration mechanism.

For drastically custom configuration requirements, you can create your own configuration object and construct the browser with it, likely using the configuration loader.

import geb.Browser
import geb.Configuration
import geb.ConfigurationLoader

def loader = new ConfigurationLoader("a-custom-environment")
def config = loader.conf
def browser = new Browser(config)

Wherever possible, you should strive to use the no-arg constructor and manage Geb through the inbuilt configuration mechanism as it offers a great deal of flexibility and separates your configuration from your code.

Geb integrations typically remove the need to construct a browser object and do this for you, leaving you to just manage the configuration.

2.1 The drive() method

The Browser class features a static method, drive(), that makes Geb scripting a little more convenient.

Browser.drive {
    go "signup"
    $("h1").text() == "Signup Page"
}

Which is equivalent to:

def browser = new Browser()
browser.go "signup"
browser.$("h1").text() == "Signup Page"

The drive() method takes all of the arguments that the
Browser constructor takes (i.e. none, a
configuration and/or property overrides) or an existing browser instance, and a
closure. The closure is evaluated against created browser instance (i.e. the browser is made the delegate of the closure). The net result is that all top level method calls and property accesses are implied to be against the browser.

The drive() method always returns the browser object that was used, so if you need to quit the browser after the drive session you can do something like…

Browser.drive("http://myapp.com") {
    …
}.quit()

For more on when/why you need to manually quit the browser, see the section on the driver

2.2 Making requests

2.2.1 The base URL

Browser instances maintain a baseUrl property that is used to resolve all non absolute URLs.
This value can come from configuration or can be
explicitly set on the browser.

Care must be taken with slashes when specifying both the base URL and the relative URL as trailing and leading slashes have significant meaning. The following table illustrates the resolution of different types of URLs.

BaseNavigating ToResult
http://myapp.com/abchttp://myapp.com/abc
http://myapp.comabchttp://myapp.comabc
http://myapp.com/abchttp://myapp.com/abc
http://myapp.com/abc/defhttp://myapp.com/abc/def
http://myapp.com/abcdefhttp://myapp.com/def
http://myapp.com/abc//defhttp://myapp.com/def
http://myapp.com/abc/def/jklhttp://myapp.com/abc/def/jkl
http://myapp.com/abc/defjklhttp://myapp.com/abc/jkl
http://myapp.com/abc/def/jklhttp://myapp.com/jkl

It is usually most desirable to define your base urls with trailing slashes and not to use leading slashes on relative URLs.

2.2.2 Using pages

Page objects (discussed further shortly) can define a url that will be used when explicitly navigating to that page. This is done with the to() and via() methods.

class SignupPage extends Page {
    static url = "signup"
}

Browser.drive {
    to SignupPage
    assert $("h1").text() == "Signup Page"
    assert page instanceof SignupPage
}

The to() and via() method makes a request to the resolved URL and sets the browser’s page instance to an instance of the given class. Most Geb scripts and tests start with a to() or via() call.

See the section on Advanced Page Navigation for more information on how to use more complicated URL resolution for pages.

2.2.3 Direct

You can also make a new request to a URL without setting or changing the page using the go() methods. The following examples use a baseUrl of “http://myapp.com/”.

Browser.drive {
    // Go to the Base URL
    go()

    // Go to a URL relative to Base URL
    go "signup"

    // Go to a URL with request params, i.e http://myapp.com/signup?param1=value1&param2=value2
    go "signup", param1: "value1", param2: "value2"
}

2.3 The Page

Browser instances hold a reference to a page. This page instance is retrievable via the page property. Initially, all browser instances have a page of type Page which provides the basic navigation functions and is the super class for all page objects.

However, the page property is rarely accessed directly. The browser object will forward any method calls or property read/writes that it can’t handle to the current page instance.

Browser.drive {
    go "signup"

    // The following two lines are equivalent
    assert $("h1").text() == "Signup Page"
    assert page.$("h1").text() == "Signup Page"
}

The page is providing the $() function, not the browser. This forwarding facilitates very concise code, void of unnecessary noise.

for more information on the $() function which is used to interact with page content, see the section on the Navigator API.

When using the Page Object pattern, you create subclasses of Page that define content via a powerful DSL that allows you to refer to content by meaningful names instead of tag names or CSS expressions.

class SignupPage extends Page {
    static url = "signup"
    static content = {
        heading { $("h1").text() }
    }
}

Browser.drive {
    to SignupPage
    assert heading == "Signup Page"
}

Page objects are discussed in depth in the pages chapter, which also explores the Content DSL.

2.3.1 Changing the page

We have already seen that that to() methods change the browser’s page instance. It is also possible to change the page instance without initiating a new request with the page() methods.

The page(Class pageType) method allows you to change the page to a new instance of the given class. The class must be Page or a subclass thereof. This method does not verify that the given page actually matches the content (at checking is discussed shortly).

The page(Class[] potentialPageTypes) method allows you to specify a number of potential page types. Each of the potential pages is instantiated and checked to see if it matches the content the browser is actually currently at by running each pages at checker. All of the page classes passed in must have an “at” checker defined otherwise an UndefinedAtCheckerException will be thrown.

These methods are not typically used explicitly but are used by the to() method and content definitions that specify the page that the content navigates to when clicked (see the section on the to attribute of the Content DSL for more information about this). However, should you need to manually change the page type they are there.

2.4 At checking

Pages define an “at checker” that the browser uses for checking if it is pointing at a given page.

class SignupPage extends Page {
    static at = {
        $("h1").text() == "Signup Page"
    }
}

Browser.drive {
    to SignupPage
}

Not using explicit return statements in “at” checkers is preffered. Geb transforms all “at” checkers so that each statement in them is asserted (just like for then: blocks in Spock specifications). Thanks to that you can immediately see evaluated values of your “at” checker if it fails. See the “at checker” section for more details.

The to() method that takes a single page type verifies that the the browser ends up at the given type. If the request may initiate a redirect and take the browser to a different page you should use via() method:

Browser.drive {
    via SecurePage
    at AccessDeniedPage
}

Browser objects have an at(Class pageType) method that tests whether or not the browser is currently at the type of page modeled by the given page object type.

The at AccessDeniedPage method call will either return a page instance or throw an AssertionError even if there are no explicit assertions in the “at” checker if the checker doesn’t pass.

It’s a good idea to always use to() method or use via() together an at() check whenever the page changes in order to fail fast. Otherwise, subsequent steps may fail in harder to diagnose ways due to the content not matching what is expected and content lookups having strange results.

If you pass a page class that doesn’t define an “at” checker to at() you will get an UndefinedAtCheckerException - “at” checkers are mandatory when doing explicit at checks. This is not the case when implicit at checks are being performed, like when using to(). This is done to make you aware that you probably want to define an “at” checker when explicitly verifing if you’re at a given page but not forcing you to do so when using implicit at checking.

Pages can also define content that declares what the browser’s page type should change to when that content is clicked. After clicking on such content page is automatically at verified (see the DSL reference for the to parameter).

class LoginPage extends Page {
    static content = {
        loginButton(to: AdminPage) { $("input", type: "submit", name: "login") }
    }
}

class AdminPage extends Page {
    static at = {
        assert $("h1").text() == "Admin Page"
    }
}

Browser.drive {
    to LoginPage
    loginButton.click()
    at AdminPage
}

The at() method will also update the browser’s page instance to the given page type if its at checker is successful.

2.5 Page change listening

It is possible to be notified when a browser’s page instance changes (note that this is not necessarily when the browser makes a request to a new URL) using the PageChangeListener interface.

import geb.PageChangeListener

class EchoingPageChangeListener implements PageChangeListener {
    void pageWillChange(Browser browser, Page oldPage, Page newPage) {
        println "browser '$browser' changing page from '$oldPage' to '$newPage'"
    }
}

def browser = new Browser()
def listener = new EchoingPageChangeListener()

browser.registerPageChangeListener(listener)

As soon as a listener is registered, its pageWillChange() method will be called with newPage as the current page and oldPage as null. Subsequently, each time the page changes oldPage will be the page that the browser currently has, and newPage will be the page that will soon be the browser’s page.

You can remove remove a listener at any time…

browser.removePageChangeListener(listener)

The removePageChangeListener(PageChangeListener listener) returns true if listener was registered and has now been removed, otherwise it returns false.

Listeners cannot be registered twice. If an attempt is made to register a listener that is already registered (i.e. there is another listener that is equal to the listener trying to register, based on their equals() implementation) then a PageChangeListenerAlreadyRegisteredException will be raised.

2.6 Working with multiple tabs and windows

When you’re working with an application that opens new windows or tabs, for example when clicking on a link with a target attribute set, you can use withWindow() and withNewWindow() methods to execute code in the context of other windows.

If you really need to know the name of the current window or all the names of open windows use getCurrentWindow() and getAvailableWindows() methods but withWindow() and withNewWindow() are the preferred methods when it comes to dealing with multiple windows.

2.6.1 Switching context to already opened windows

If you know the name of the window in which context you want to execute the code you can use withWindow(String windowName, Closure block). Given this html:

<a href="http://www.gebish.org" target="myWindow">Geb</a>

This code passes:

$('a').click()
withWindow('myWindow') {
    assert $('title').text() == 'Geb - Very Groovy Browser Automation'
}

If you don’t know the name of the window but you know something about the content of the window you can use the withWindow(Closure specification, Closure block) method. The first closure passed should return true for the window, or windows, you want to use as context. Note that if there is no window for which the window specification closure returns true then NoSuchWindowException is thrown. So given:

<a href="http://www.gebish.org" target="_blank">Geb</a>

This code passes:

$('a').click()
withWindow({ $('title').text() == 'Geb - Very Groovy Browser Automation' }) {
    assert $('#slogan').text() == 'very groovy browser automation… web testing, screen scraping and more'
}

2.6.1.1 Passing options when working with already opened windows

Currently there is only one option that can be passed to a withWindow() call which make working with already opened windows even simpler. The general syntax is:

withWindow({ «window specification» }, «option name»: «option value», ...) { «action executed within the context of the window» }
2.6.1.1.1 close

Default value: false

If you pass any truly value as close option then all matching windows will be closed after the execution of the closure passed as the last argument to the withWindow() call.

2.6.1.1.2 page

Default value: null

If you pass a class that extends Page as page option then browser’s page will be set to that value before executing the closure passed as the last argument and will be reverted to it’s original value afterwards.

2.6.2 Switching context to newly opened windows

If you wish to execute code in a window that is newly opened by some of your actions use the withNewWindow(Closure windowOpeningBlock, Closure block) method. Given html as above the following will pass:

withNewWindow({ $('a').click() }) {
    assert $('title').text() == 'Geb - Very Groovy Browser Automation'
}

Note that if the first parameter opens none or more than one window then NoNewWindowException is thrown.

2.6.2.1 Passing options when working with newly opened windows

There are several options that can be passed to a withNewWindow() call which make working with newly opened windows even simpler. The general syntax is:

withNewWindow({ «window opening action» }, «option name»: «option value», ...) { «action executed within the context of the window» }
2.6.2.1.1 close

Default value: true

If you pass any truly value as close option then the newly opened window will be closed after the execution of the closure passed as the last argument to the withNewWindow() call.

2.6.2.1.2 page

Default value: null

If you pass a class that extends Page as page option then browser’s page will be set to that value before executing the closure passed as the last argument and will be reverted to it’s original value afterwards.

2.6.2.1.3 wait

Default value: null

You can specify wait option if the action defined in the window opening closure passed as the first argument is asynchronous and you need to wait for the new window to be opened. The possible values for the wait option are consistent with the ones for content definition and can be one of the following:

Given the following html:

<a href="http://www.gebish.org" target="_blank" id="new-window-link">Geb</a>

the following will pass:

withNewWindow({
    js.exec """
         setTimeout(function() {
          document.getElementById('new-window-link').click();
         }, 200);
       """
}, wait: true) {
    assert $('title').text() == 'Geb - Very Groovy Browser Automation'
}

2.7 Quitting the browser

The browser object has quit()
and close() methods (that simply delegate to the underlying driver). See the section on driver management for more information on when and why you need to quit the browser.

3 The WebDriver Implementation

A geb.Browser instance interacts with an actual browser via an instance of WebDriver. The browser’s driver can always be retrieved via the WebDriver getDriver() method.

One of the key design principles that WebDriver embraces is that tests/scripts should be written to the WebDriver API making them agnostic to the actual browser being driven, and therefore portable. Geb always supports this goal. However, the reality is that there are still quirks and behavioural differences between driver implementations. Each release of WebDriver has historically worked to minimise these issues so expect the situation to improve over time as WebDriver matures.

3.1 Explicit Driver Management

One option for specifying the driver implementation is to construct the driver instance and pass it to the geb.Browser to be used when it is constructed.

However, where possible prefer implicit driver management which is discussed later in this chapter.

3.1.1 Explicit Lifecycle

When the driver is constructed by the user, the user is responsible for quitting the driver at the appropriate time. This can be done via the methods on the webdriver instance (obtainable via geb.Browser#getDriver()) or by calling the delegating methods on the browser object.

3.2 Implicit Driver Management

If a driver is not given when a Browser object is constructed, one will be created and managed implicitly by Geb by the the configuration mechanism.

3.2.1 Implicit Lifecycle

By default, Geb internally caches and reuses the first driver created, meaning that all subsequent browser instances created without an explicit driver will reuse the cached driver. This avoids the overhead of creating a new driver each time which can be significant when working with a real browser.

This means that you may need to call the clearCookies() method on the browser in order to not get strange results due to cookies from previous executions.

Note that some of the integrations (e.g. Spock, JUnit) automatically clear the browser cookies at appropriate times such as after each test. Consult the section on testing for specifics.

The shared driver will be closed and quitted when the JVM shuts down.

A new driver can be forced at anytime by calling either of the following static methods on the CachingDriverFactory class…

import geb.driver.CachingDriverFactory

def cachedDriver = CachingDriverFactory.clearCache()
def cachedDriver = CachingDriverFactory.clearCacheAndQuitDriver()

After calling either of this methods, the next request for a default driver will result in a new driver instance being created.

This caching behavior is configurable.

3.3 Driver Quirks

This section details various quirks or issues that have been encountered with different driver implementations.

3.3.1 HTMLUnitDriver

3.3.1.1 Dealing with pages that use HTML refreshes

The default behaviour of the HTMLUnit driver is to immediately refresh the page as soon as it encounters a <meta http-equiv="refresh" content="5"> regardless of the specified time. The solution is to use a refresh handler that handles the refresh asynchronously.

import com.gargoylesoftware.htmlunit.ThreadedRefreshHandler

Browser.drive {
  driver.webClient.refreshHandler = new ThreadedRefreshHandler()
  …
}

See this mailing list thread for details.

3.3.1.2 Configuring logging

HTMLUnit can be very noisy, and it’s not clear how to make it not so noisy.

See this issue for some tips on how to tune its logging.

4 Interacting with content

Geb provides a concise and Groovy interface to the content and controls in your browser. This is implemented through the Navigator API which is a jQuery inspired mechanism for finding, filtering and interacting with DOM elements.

4.1 The $ Function

The $ function is the access point to the browser’s page content. This returns a geb.navigator.Navigator object that is roughly analogous to a jQuery object. It is analogous in that it represents one or more elements on the page and can be used to refine the matched content or query the matched content. When a $ function is called that does not match any content, an “empty” navigator object is returned that represents no content. Operations on “empty” navigators return null or another “empty” navigator or other values that make sense (e.g. the size() method returns 0).

The signature of the $ function is as follows…

$(«css selector», «index or range», «attribute / text matchers»)

The following is a concrete example…

$("h1", 2, class: "heading")

This would find the 3rd (elements are 0 indexed) h1 element whose class attribute is exactly “heading”.

All arguments are optional, meaning the following calls are all valid:

$("div p", 0)
$("div p", title: "something")
$(0)
$(title: "something")

There is an alias for the dollar function named “find” if a method named “$” is not to your test (a current limitation of Groovy prevents us supporting a find() method like $() though, this will be fixed in later versions).

4.1.1 CSS Selectors

You can use any CSS selector that the underlying WebDriver supports…

$("div.some-class p:first[title='something']")

In the case of the HTMLUnit driver, which does not support CSS selectors at all, only basic CSS 2 type selectors can be used. A future version of the HTMLUnit driver may gain better CSS selector support.

4.1.2 Indexes and Ranges

When matching, a single positive integer or integer range can be given to restrict by index.

Consider the following html…

<p>a</p>
<p>b</p>
<p>c</p>

We can use indexes to match content like so…

$("p", 0).text() == "a"
$("p", 2).text() == "c"
$("p", 0..1)*.text() = ["a", "b"]
$("p", 1..2)*.text() = ["b", "c"]

See below for an explanation of the text() method and the use of the spread operator.

4.1.3 Attribute and Text Matching

Matches can be made on attributes and node text values via Groovy’s named parameter syntax. The value text is treated specially as a match against the node’s text. All other values are matched against their corresponding attribute values.

Consider the following html…

<p attr1="a" attr2="b">p1</p>
<p attr1="a" attr2="c">p2</p>

We can use attribute matchers like so…

$("p", attr1: "a").size() == 2
$("p", attr2: "c").size() == 1

Attribute values are anded together…

$("p", attr1: "a", attr2: "b").size() == 1

We can use text matchers like so…

$("p", text: "p1").size() == 1

You can mix attribute and text matchers…

$("p", text: "p1", attr1: "a").size() == 1

4.1.3.1 Using Patterns

To match the entire value of an attribute or the text you use a String value. It is also possible to use a Pattern to do regexp matching…

$("p", text: ~/p./).size() == 2

Geb also ships with a bunch of shortcut pattern methods…

$("p", text: startsWith("p")).size() == 2
$("p", text: endsWith("2")).size() == 1

The following is the complete listing:

Case SensitiveCase InsensitiveDescription
startsWithiStartsWithMatches values that start with the given value
containsiContainsMatches values that contain the given value anywhere
endsWithiEndsWithMatches values that end with the given value
containsWordiContainsWordMatches values that contain the given value surrounded by either whitespace or the beginning or end of the value
notStartsWithiNotStartsWithMatches values that DO NOT start with the given value
notContainsiNotContainsMatches values that DO NOT contain the given value anywhere
notEndsWithiNotEndsWithMatches values that DO NOT end with the given value
notContainsWordiNotContainsWordMatches values that DO NOT contain the given value surrounded by either whitespace or the beginning or end of the value

All of these methods themselves can take a String or a Pattern

$("p", text: contains(~/\d/)).size() == 2

You might be wondering how this magic works, i.e. where these methods come from and where they can be used. They are methods that are available on geb.Page and other places where you can use the $ function. They simply just return patterns.

4.1.4 Navigators are Iterable

The navigator objects implement the Java Iterable interface, which allows you to do lots of Groovy stuff like use the max() function…

<p>1</p>
<p>2</p>

$("p").max { it.text() }.text() == "2"

This also means that navigator objects work with the Groovy spread operator…

$("p")*.text().max() == "2"

When treating a navigator as Iterable, the iterated over content is always the exact matched elements (as opposed to including children).

4.2 Finding & Filtering

Navigator objects have a find method for finding descendants, and filter and not methods for reducing the matched content.

Consider the following html…

<div class="a">
    <p class="b">geb</p>
</div>
<div class="b">
    <input type="text"/>
</div>

We can select p.b by…

$("div").find(".b")

We can select div.b by…

$("div").filter(".b")

or…

$(".b").not("p")

We can select the div containing the p with…

$("div").has("p")

Or select the div containing the input with a type attribute of “text” like so…

$("div").has("input", type: "text")

The find and method supports the exact same argument types as the $ function.

The filter, not and has methods have the same signatures - they accept: a selector string, a predicates map or both.

These methods return a new navigator object that represents the new content.

4.3 Traversing

Navigators also have methods for selecting content around the matched content.

Consider the following html…

<div class="a">
    <div class="b">
        <p class="c"></p>
        <p class="d"></p>
        <p class="e"></p>
    </div>
    <div class="f"></div>
</div>

You can select content around p.d by…

$("p.d").previous() // 'p.c'
$("p.e").prevAll() // 'p.c' & 'p.d'
$("p.d").next() // 'p.e'
$("p.c").nextAll() // 'p.d' & 'p.e'
$("p.d").parent() // 'div.b'
$("p.c").siblings() // 'p.d' & 'p.e'
$("div.a").children() // 'div.b' & 'div.f'

Consider the following html…

<p class="a"></p>
<p class="b"></p>
<p class="c"></p>

The following code will select p.b & p.c

$("p").next()

The previous, prevAll, next, nextAll, parent, parents, closest, siblings and children methods can also take css selectors and attribute matchers.

Using the same html, the following code will select p.c

$("p").next(".c")

Likewise, consider the following html…

<div class="a">
    <div class="b">
        <p></p>
    </div>
</div>

The following code will select div.b

$("p").parent(".b")

The closest method is a special case in that it will select the first ancestors of the current elements that match a selector. There is no no-argument version of the closest method. For example, this will select div.a

$("p").closest(".a")

These methods do not take indexes as they automatically select the first matching content. To select multiple elements you can use prevAll, nextAll and parents all of which have no-argument versions and versions that filter by a selector.

The nextUntil, prevUntil and parentsUntil methods return all nodes along the relevant axis until the first one that matches a selector. Consider the following markup:

<div class="a"></div>
<div class="b"></div>
<div class="c"></div>
<div class="d"></div>

The following code will select div.b and div.c:

$(".a").nextUntil(".d")

4.4 Composition

It is also to compose navigator objects from other navigator objects, for situations where you can’t express a content set in one query. To do this, simply call the $ function with the navigators to use…

$($("div.a"), $("div.d"))

This will return a new navigator object that represents only the a and d divs.

You can compose navigator objects from content. So given a page content definition:

static content = {
    divElement { divClass -> $('p', 'class': divClass) }
}

And a call:

$(divElement('a'), divElement('d'))

You will get a navigator that contains the same elements as the one above.

4.5 Clicking

Navigator objects implement the click() method, which will instruct the browser to click only the first item the navigator has matched.

There are also click(Class) and click(List<Class>) methods that are analogous to the browser object’s page(Class) and page(List<Class>) methods respectively. This allow page changes to be specified at the same time as click actions.

For example…

$("input.loginButton").click(LoginPage)

Would click the “input.loginButton” element, then effectively call browser.page(LoginPage) and verify that the browser is at the expected page.

All of the page classes passed in when using the list variant have to have an “at” checker defined otherwise an UndefinedAtCheckerException will be thrown.

4.6 Determining Visibility

Navigator objects have a displayed property that indicates whether the element is visible to the user or not. The displayed property of a navigator object that doesn’t match anything is always false

4.7 Size and Location

You can obtain the size and location of content on the page. All units are in pixels. The size is available via the height and width properties, while the location is available as the x and y properties which represent the distance from the top left of the page (or parent frame) to the top left point of the content.

All of these properties operate on the first matched element only.

$("div").height == 20
$("div").width == 40
$("div").x == 60
$("div").y == 80

To obtain any of the properties for all matched elements, you can use the Groovy spread operator.

$("div")*.height == [20, 30]
$("div")*.width == [40, 50]
$("div")*.x == [60, 70]
$("div")*.y == [80, 90]

4.8 Accessing tag name, attributes, text and classes

The tag(), text(), @attribute and classes() methods return the requested content on the first matched content. The classes() method returns a java.util.List of unique class names sorted alphabetically.

Consider the following HTML…

<p title="a" class="a para">a</p>
<p title="b" class="b para">b</p>
<p title="c" class="c para">c</p>

The following assertions are valid…

$("p").text() == "a"
$("p").tag() == "p"
$("p").@title == "a"
$("p").classes() == ["a", "para"]

To obtain information about all matched content, you use the Groovy spread operator

$("p")*.text() == ["a", "b", "c"]
$("p")*.tag() == ["p", "p", "p"]
$("p")*.@title == ["a", "b", "c"]
$("p")*.classes() == [["a", "para"], ["b", "para"], ["c", "para"]]

4.9 Sending keystrokes

Keystrokes can be sent to any content via the leftShift operator, which is a shortcut for the [sendKeys()](http://selenium.googlecode.com/svn/trunk/docs/api/java/org/openqa/selenium/WebElement.html#sendKeys(java.lang.CharSequence...\)) method of WebDriver.

$("div") << "abc"

How content responds to the keystrokes depends on what the content is.

4.9.1 Non characters (e.g. delete key)

It is possible to send non textual characters to content by using the WebDriver Keys enumeration…

import org.openqa.selenium.Keys

$("input", name: "firstName") << Keys.chord(Keys.CONTROL, "c")

Here we are sending a “control-c” to an input.

See the documentation for Keys for more information on the possible keys.

4.10 Accessing input values

The value of input, select and textarea elements can be retrieved and set with the value method. Calling value() with no arguments will return the String value of the first element in the Navigator. Calling value(value) will set the current value of all elements in the Navigator. The argument can be of any type and will be coerced to a String if necessary. The exceptions are that when setting a checkbox value the method expects a boolean (or, an existing checkbox value) and when setting a multiple select the method expects an array or Collection of values.

4.11 Form Control Shortcuts

Interacting with form controls (input, select etc.) is such a common task in web functional testing that Geb provides convenient shortcuts for common functions.

Geb supports the following shortcuts for dealing with form controls.

Consider the following HTML…

<form>
    <input type="text" name="geb" value="testing" />
</form>

The value can be read and written via property notation…

$("form").geb == "testing"
$("form").geb = "goodness"
$("form").geb == "goodness"

These are literally shortcuts for…

$("form").find("input", name: "geb").value() == "testing"
$("form").find("input", name: "geb").value("goodness")
$("form").find("input", name: "geb").value() == "goodness"

There is also a shortcut for obtaining a navigator based on a control name

$("form").geb()

Which is literally a shortcut for…

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

In the above and below examples with form controls we are using code like $("form").someInput where we could be using just someInput as long as there is only one control with the name someInput on the page. In the examples we are using $("form").someInput to hopefully be clearer.

If your content definition (either a page or a module) describes content which is an input, select or textarea you can access and set its value the same way as described above for forms. Given a page and module definitions for the above mentioned html:

class ShortcutModule extends Module {
    static content = {
       geb { $('form').geb() }
    }
}

static content = {
    geb { $('form').geb() }
    shortcutModule { module ShortcutModule }
}

The following will pass:

assert geb == "testing"
geb = "goodness"
assert geb == "goodness"

As well as:

assert shortcutModule.geb == "testing"
shortcutModule.geb = "goodness"
assert shortcutModule.geb == "goodness"

The following examples describe usage of form controls only using code like $("form").someInput. Given a content definition myContent { $("form").someInput } you can substitute $("form").someInput in the examples with myContent.

4.11.1 Setting Values

4.11.1.1 select

Select values are set by assigning the value or text of the required option. Assigned values are automatically coerced to String. For example…

<select name="artist">
    <option value="1">Ima Robot</option>
    <option value="2">Edward Sharpe and the Magnetic Zeros</option>
    <option value="3">Alexander</option>
</select>

We can select options with…

$("form").artist = "1"         // first option selected by its value attribute
$("form").artist = 2           // second option selected by its value attribute
$("form").artist = "Ima Robot" // first option selected by its text

If you attempt to set a select to a value that does not match the value or text of any options, an IllegalArgumentException will be thrown.

4.11.1.2 multiple select

If the select has the multiple attribute it is set with a array or Collection of values. Any options not in the values are un-selected. For example…

<select name="genres" multiple>
    <option value="1">Alt folk</option>
    <option value="2">Chiptunes</option>
    <option value="3">Electroclash</option>
    <option value="4">G-Funk</option>
    <option value="5">Hair metal</option>
</select>

We can select options with…

$("form").genres = ["2", "3"]                 // second and third options selected by their value attributes
$("form").genres = [1, 4, 5]                  // first, fourth and fifth options selected by their value attributes
$("form").genres = ["Alt folk", "Hair metal"] // first and last options selected by their text
$("form").genres = []                         // all options un-selected

If the collection being assigned contains a value that does not match the value or text of any options, an IllegalArgumentException will be thrown.

4.11.1.3 checkbox

Checkboxes are generally checked/unchecked by setting their value to true or false.

You can also select a checkbox by explicitly setting its value. This is useful when you have a number of checkboxes with the same name, i.e.

<input type="checkbox" name="pet" value="dogs" />
<input type="checkbox" name="pet" value="cats" />

You can select dogs as your pet type, as follows:

$("checkbox", name: "pet").value("dogs")

Calling value() on a checked checkbox will return the value of its value attribute, i.e:

<input type="checkbox" name="pet" value="dogs" checked="checked"/>

assert $("checkbox", name: "pet").value() == "dogs"

Calling value() on an unchecked checkbox will return false, i.e:

<input type="checkbox" name="pet" value="dogs"/>

assert $("checkbox", name: "pet").value() == false

In general you should use Groovy Truth when checking if a checkbox is checked:

if ($("checkbox", name: "pet").value()) {
    //evaluated only if "pet" checkbox is checked
}

4.11.1.4 radio

Radio values are set by assigning the value of the radio button that is to be selected or the label text associated with a radio button.

For example, with the following radio buttons…

<label for="site-current">Search this site</label>
<input type="radio" id="site-current" name="site" value="current">

<label>Search Google
    <input type="radio" name="site" value="google">
</label>

We can select the radios with…

$("form").site = "current"          // selects the first radio by its value
$("form").site = "Search this site" // selects the first radio by its label
$("form").site = "Search Google"    // selects the second radio by its label

4.11.1.5 text inputs and textareas

In the case of a text input, the assigned value becomes the input’s value attribute and for a textarea effectively becomes the text.

It is also possible to append text by using the send keys shorthand…

<input name="language" value="gro" />

$("form").language() << "ovy"
assert $("form").language == "groovy"

Which an also be used for non character keys…

<input name="postcode" />

import org.openqa.selenium.Keys

$("form").postcode = "12345"
$("form").postcode() << Keys.BACK_SPACE
assert $("form").postcode == "1234"

Note that WebDriver has some issues with textareas and surrounding whitespace. Namely, some drivers implicit trim whitespace from the beginning and end of the value. You can track this issue here: http://code.google.com/p/selenium/issues/detail?id=2131

4.11.1.6 file upload

It’s currently not possible with WebDriver to simulate the process of a user clicking on a file upload control and choosing a file to upload via the normal file chooser. However, you can directly set the value of the upload control to the absolute path of a file on the system where the driver is running and on form submission that file will be uploaded.

<input type="file" name="csvFile">

$("form").csvFile = "/path/to/my/file.csv"

4.12 Complex Interactions

WebDriver supports interactions that are more complex than simply clicking or typing into items, such as dragging. You can use this API from Geb, or use the more Geb friendly interact {} DSL (explained below).

4.12.1 Using the WebDriver API directly

A Geb navigator object is built on top of a collection of WebDriver WebElement objects. It is possible to access the contained WebElements via the following methods on navigator objects:

WebElement firstElement()
WebElement lastElement()
Collection<WebElement> allElements()

By using the methods of the WebDriver Actions class with WebElements, complex user gestures can be emulated.

4.12.2 Using Actions

Create an Actions instance after obtaining the WebDriver driver:

def actions = new Actions(driver)

Next, use methods of Actions to compose a series of UI actions, then call build() to create a concrete Action:

import org.openqa.selenium.Keys

WebElement someItem = $('li.clicky').firstElement()
def shiftDoubleClickAction = actions.keyDown(Keys.SHIFT).doubleClick(someItem).keyUp(Keys.SHIFT).build()

Finally, call perform() to actually trigger the desired mouse or keyboard behavior:

shiftDoubleClickAction.perform()

4.12.3 Using Interact Closures

To cut down on the amount of typing required, use an interact closure instead of using class Actions explicitly. When using an interact closure, an Actions instance is implicitly created, built into an Action, and performed. As an added bonus, Geb navigators can be passed directly to Actions methods within an interact closure.

This interact closure performs the same work as the calls in the ‘Using Actions’ section:

import org.openqa.selenium.Keys

interact {
    keyDown(Keys.SHIFT)
    doubleClick($('li.clicky'))
    keyUp(Keys.SHIFT)
}

This method creates code that is more readable than using Actions directly.

For the full list of available interactions, see the documentation for the WebDriver Actions class.

4.12.4 Interact Examples

Interact closures (or Actions) can be used to perform behaviors that are more complicated than clicking buttons and anchors or typing in input fields. Shift-double-clicking was demonstrated earlier.

4.12.4.1 Drag and Drop

clickAndHold, moveByOffset, and then release will drag and drop an element on the page.

interact {
    clickAndHold($('#element'))
    moveByOffset(400, -150)
    release()
}

Drag-and-dropping can also be accomplished using the dragAndDropBy convenience method from the Actions API:

interact {
    dragAndDropBy($('#element'), 400, -150)
}

In this particular example the element will be clicked then dragged 400 pixels to the right and 150 pixels upward before being released.

Note that moving to arbitrary locations with the mouse is currently not supported by the HTMLUnit driver, but moving directly to elements is.

4.12.4.2 Control-Clicking

Control-clicking several elements, such as items in a list, is performed the same way as shift-clicking.

import org.openqa.selenium.Keys

interact {
    keyDown(Keys.CONTROL)
    click($('ul.multiselect li', text: 'Order 1'))
    click($('ul.multiselect li', text: 'Order 2'))
    click($('ul.multiselect li', text: 'Order 3'))
    keyUp(Keys.CONTROL)
}

5 Pages

Before reading this page, please make sure you have read the section on the Browser.drive() method

5.1 The Page Object pattern

import geb.Browser

Browser.drive {
    go "http://google.com/ncr"
    $("input[name=q]").value "Chuck Norris"
    $("input[value='Google Search']").click()
    waitFor { $("li.g", 0).find("a.l").text().contains("Chuck") }
}

This is valid Geb code, and it works well for a one off script but there are two big issues with this approach. Imagine that you have many tests that involve searching and checking results. The implementation of how to search and how to find the results is going to have to be duplicated in every test, maybe many times per test. As soon as something as trivial as the name of the search field changes you have to update a lot of code. The Page Object Pattern allows us to apply the same principles of modularity, reuse and encapsulation that we use in other aspects of programming to avoid such issues in browser automation code.

Here is the same script, utilising page objects…

import geb.*

class GoogleHomePage extends Page {
    static url = "http://google.com/?complete=0"
    static at = { title == "Google" }
    static content = {
        searchField { $("input[name=q]") }
        searchButton(to: GoogleResultsPage) { $("input[value='Google Search']") }
    }

    void search(String searchTerm) {
        searchField.value searchTerm
        searchButton.click()
    }
}

class GoogleResultsPage extends Page {
    static at = { waitFor { title.endsWith("Google Search") } }
    static content = {
        results(wait: true) { $("li.g") }
        result { index -> results[index] }
        resultLink { index -> result(index).find("a.l") }
    }
}

// Now the script
Browser.drive {
    to GoogleHomePage
    search "Chuck Norris"
    at GoogleResultsPage
    resultLink(0).text().contains("Chuck")
}

You have now encapsulated, in a reusable fashion, information about each page and how to interact with it. As anyone who has tried to knows, maintaining a large suite of functional web tests for a changing application can become an expensive and frustrating process. Geb’s support for the Page Object pattern addresses this problem.

5.2 The Page superclass

All page objects must inherit from Page.

5.3 The Content DSL

Geb features a DSL for defining page content in a templated fashion, which allows very concise yet flexible page definitions. Pages define a static closure property called content that describes the page content.

Consider the following HTML…

<div id="a">a</div>

We could define this content as so…

class ExamplePage extends Page {
    static content = {
        theDiv { $('div', id: 'a') }
    }
}

The structure to the content DSL is…

«name» { «definition» }

Where «definition» is Groovy code that is evaluated against the instance of the page.

Here is how it could be used…

Browser.drive {
    to ExamplePage
    assert theDiv.text() == "a"
}

So how is this working? First, remember that the Browser instance delegates any method calls or property accesses that it doesn’t know about to the current page instance. So the above code is the same as…

Browser.drive {
    to ExamplePage
    assert page.theDiv.text() == "a"
}

Secondly, defined content becomes available as properties and methods on instance of the page…

Browser.drive {
    to ExamplePage

    // Following two lines are equivalent
    assert theDiv.text() == "a"
    assert theDiv().text() == "a"
}

The Content DSL actually defines content templates. This is best illustrated by example…

class ExamplePage extends Page {
    static content = {
        theDiv { id -> $('div', id: id) }
    }
}

Browser.drive {
    to ExamplePage
    assert theDiv("a").text() == "a"
}

There are no restrictions on what arguments can be passed to content templates.

A content template can return anything. Typically they will return a Navigator object through the use of the $() function, but it can be anything.

class ExamplePage extends Page {
    static content = {
        theDivText { $('div#a').text() }
    }
}

Browser.drive {
    to ExamplePage
    assert theDivText == "a"
}

It’s important to realise that «definition» code is evaluated against the page instance. This allows code like the following…

class ExamplePage extends Page {
    static content = {
        theDiv { $('div#a') }
        theDivText { theDiv.text() }
    }
}

And this is not restricted to other content…

class ExamplePage extends Page {
    def divId = a
    static content = {
        theDiv { $('div', id: divId) }
        theDivText { theDiv.text() }
    }
}

Or…

class ExamplePage extends Page {
    static content = {
        theDiv { $('div', id: getDivId()) }
        theDivText { theDiv.text() }
    }
    def getDivId() {
        "a"
    }
}

5.3.1 Template Options

Template definitions can take different options. The syntax is…

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

For example…

theDiv(cache: false, required: false) { $("div", id: "a") }

The following are the available options.

5.3.1.1 required

Default value: true

The required option controls whether or not the content returned by the definition has to exist or not. This is only relevant when the definition returns a Navigator object (via the $() function), it is ignored if the definition returns anything else.

If the required option is set to true and the returned content does not exist, a RequiredPageContentNotPresent exception will be thrown.

class ExamplePage extends Page {
    static content = {
        theDiv { $('div', id: "b") }
    }
}

Browser.drive {
    to ExamplePage
    def thrown = false
    try {
        theDiv
    } catch (RequiredPageContentNotPresent e) {
        thrown = true
    }
    assert thrown
}

5.3.1.2 cache

Default value: false

The cache option controls whether or not the definition is evaluated each time the content is requested (the content is cached for each unique set of parameters).

class ExamplePage extends Page {
    def value = 1
    static content = {
        theValue(cache: true) { value }
    }
}

Browser.drive {
    to ExamplePage
    assert theValue == 1
    value = 2
    assert theValue == 1
}

With caching disabled…

class ExamplePage extends Page {
    def value = 1
    static content = {
        theValue(cache: false) { value }
    }
}

Browser.drive {
    to ExamplePage
    assert theValue == 1
    value = 2
    assert theValue == 2
}

Caching is a performance optimisation and is disabled by default. You may want to enable if you notice that the a particular content definition is taking a long time to resolve.

5.3.1.3 to

Default value: null

The to option allows the definition of which page the browser will be sent to if the content is clicked.

class ExamplePage extends Page {
    static content = {
        helpLink(to: HelpPage) { $("a", text: "Help") }
    }
}

class HelpPage extends Page {}

Browser.drive {
    to ExamplePage
    helpLink.click()
    assert at(HelpPage)
}

The to value will be implicitly used as an argument to the content’s click() method, effectively setting the new page type and verifying its at checker. See the section on clicking content for how this changes the browser’s page object.

The list variant can also be used…

static content = {
    loginButton(to: [LoginSuccessfulPage, LoginFailedPage]) { $("input.loginButton") }
}

Which on click sets the browser’s page to be the first page in the list whose at checker returns true. This is equivalent to the page(Class[] potentialPageTypes) browser method which is explained in the section on
changing pages.

All of the page classes passed in when using the list variant have to have an “at” checker defined otherwise an UndefinedAtCheckerException will be thrown.

5.3.1.4 wait

Default value: false

The wait option allows Geb to wait an amount of time for content to appear on the page, instead of throwing a RequiredPageContentNotPresent exception if the content is not present when requested.

class DynamicPage extends Page {
    static content = {
        dynamicallyAdded(wait: true) { $("p.dynamic") }
    }
}

Browser.drive {
    to DynamicPage
    assert dynamicallyAdded.text() == "I'm here now"
}

This is equivalent to:

class DynamicPage extends Page {
    static content = {
        dynamicallyAdded(required: false) { $("p.dynamic") }
    }
}

Browser.drive {
    to DynamicPage
    assert waitFor { dynamicallyAdded }.text() == "I'm here now"
}

See the section on waiting for the semantics of the waitFor() method, that is used here internally. Like waitFor() a WaitTimeoutException will be thrown if the wait timeout expires.

The value for the wait option can be one of the following:

Any other value will be interpreted as false.

It is also possible to use wait when defining non element content, such as a string or number. Geb will wait until the content definition returns a value that conforms to the Groovy Truth.

class DynamicPage extends Page {
    static content = {
        status { $("p.status") }
        successStatus(wait: true) { status.text().contains("Success") }
    }
}

Browser.drive {
    to DynamicPage
    assert successStatus
}

In this case, we are inherently waiting for the status content to be on the page and for it to contain the string “Success”. If the status element is not present when we request successStatus, the RequiredPageContentNotPresent exception that would be thrown is swallowed and Geb will try again after the retry interval has expired.

You can modify the behaviour of content with wait option set to true if you use it together with required option set to false. Given a content definition:

static content = {
    dynamicallyAdded(wait: true, required: false) { $("p.dynamic") }
}

Then if wait timeout expires when retrieving dynamicallyAdded there will be no WaitTimeoutException thrown and the last closure evaluation value will be returned. If there is an exception thrown during closure evaluation it will be wrapped in an UnknownWaitForEvaluationResult instance and returned.

Waiting content blocks are subject to “implicit assertions”. See the section on implicit assertions for more information.

5.3.1.5 page

Default value: null

The page option allows the definition of a page the browser will be set to if the content describes a frame and is used in a withFrame() call.

Given the following html…

<html>
    <body>
        <frame id="frame-id" src="frame.html"></frame>
    <body>
</html>

…and the code for frame.html…

<html>
    <body>
        <span>frame text</span>
    </body>
</html>

…the following will pass…

class PageWithFrame extends Page {
    static content = {
        myFrame(page: FrameDescribingPage) { $('#frame-id') }
    }
}

class FrameDescribingPage extends Page {
    static content = {
        frameContentsText { $('span').text() }
    }
}

to PageWithFrame
withFrame(myFrame) {
    assert frameContentsText == 'frame text'
}

5.3.2 Aliasing

If you wish to have the same content definitions available under diferent names you can create a content definition that specifies aliases parameter:

class AliasingPage extends Page {
    static content = {
       someButton { $("button", text: "foo") }
       someButtonByAnotherName(aliases: someButton)
    }
}

Browser.drive {
    to AliasingPage
    assert someButton.text() == someButtonByAnotherName.text()
}

Remember that the aliased content has to be defined before the aliasing content, otherwise you will get a InvalidPageContent exception.

5.4 “At” Verification

Each page can define a way to check whether the underling browser is at the page that the page class actually represents. This is done via a static at closure…

class ExamplePage extends Page {
    static at = { $("h1").text() == "Example" }
}

This closure can either return a false value or throw an AssertionError (via the assert method). The verifyAt() method call will either return true or throw an AssertionError even if there are no explicit assertions in the “at” checker.

Browser.drive {
    to ExamplePage
    verifyAt()
}

The verifyAt() method is used by the browser at() method which also returns true or throws an AssertionError even if there are no explicit assertions in the “at” checker…

Browser.drive {
    to ExamplePage
    at(ExamplePage)
}

At checkers are subject to “implicit assertions”. See the section on implicit assertions for more information.

If you don’t wish to get an exception when “at” checking fails there are methods that return false in that case: Page#verifyAtSafely() and Browser#isAt(Class<? extends Page>).

As mentioned previously, when a content template defines a “to” option of more than one page the page’s verifyAt() method is used to determine which one of the pages to use. In this situation, any AssertionErrors thrown by at checkers are suppressed.

The “at” checker is evaluated against the page instance, and can access defined content or any other variables or methods…

class ExamplePage extends Page {
    static at = { heading == "Example" }
    static content = {
        heading { $("h1").text() }
    }
}

If a page does not have an “at” checker, the verifyAt() method will throw an UndefinedAtCheckerException. The same will happen if any of the pages in a list passed to content template “to” option doesn’t define an “at” checker.

It can sometimes prove useful to wrap at verification in waitFor calls by default - some drivers are known to return control after url change before the page is fully loaded in some circumstances or before one might consider it to be loaded. This can be configured via atCheckWaiting option.

5.4.1 Unexpected pages

A list of unexpected pages can be provided via unexpectedPages configuration option.

Note that this feature does not operate on HTTP response codes as these are not exposed by WebDriver thus Geb does not have access to them. To use this feature your application has to render custom error pages that can be modeled as Page classes and detected by an at checker.

If configured, the classes from the unexpectedPages list will be checked for first when ”at“ checking is performed for any page and an UnexpectedPageException with an appropriate message will be raised if any of them is encountered.

Given that your application renders a custom error page when a page is not found and a 404 HTTP response code is returned with a text like “Sorry but we could not find that page” you can model that page with a class:

class PageNotFoundPage extends Page {

    static at = { $('#errorMessage').text() == 'Sorry but we could not find that page' }
}

Then register that page in configuration:

unexpectedPages = [PageNotFoundPage]

When checking if the browser is at a page…

at ExpectedPage

..but the at checker for PageNotFoundPage matches an UnexpectedPageException will be raised with the following message: “An unexpected page PageNotFoundPage was encountered when expected to be at ExpectedPage”.

Unexpected pages will be checked for whenever ”at“ checking is performed, even implicitly like when using to content template option or passing one or many Page classes to Navigator’s click() method.

Finally you can still explicitly check if the browser is at an unexpected page if you need to. Following will pass without throwing an UnexpectedPageException if ”at“ checking for PageNotFoundPage succeeds:

at PageNotFoundPage

5.5 Page URLs

Pages can define URLs via the static url property.

class ExamplePage extends Page {
    static url = "examples"
}

The url is used when using the browser to() method.

Browser.drive {
    go "http://myapp.com/"
    to ExamplePage
}

See the section on the base url for notes about urls and slashes.

5.6 Advanced Page Navigation

Page classes can customise how they generate URLs when used in conjunction with the browser to() method.

Consider the following example…

import geb.*

class ExamplePage extends Page {
    static url = "example"
}

Browser.drive("http://myapp.com/") {
    to ExamplePage
}

This will result in a request being made to “http://myapp.com/example”.

The to() method can also take arguments…

Browser.drive("http://myapp.com") {
    to ExamplePage, 1, 2
}

This will result in a request being made to “http://myapp.com/example/1/2”. This is because by default, any arguments passed to the to() method after the page class are converted to a URL path by calling toString() on each argument and joining them with “/”.

However, this is extensible. You can specify how a set of arguments is converted to a URL path to be added to the page URL. This is done by overriding the convertToPath() method.
The Page implementation of this method looks like this…

String convertToPath(Object[] args) {
    args ? '/' + args*.toString().join('/') : ""
}

You can either overwrite this catch all method control path conversion for all invocations, or provide an overloaded version for a specific type signature. Consider the following…

class Person {
    Long id
    String name
}

class PersonPage {
    static url = "person"

    String convertToPath(Person person) {
        person.id.toString()
    }
}

def newPerson = new Person(id: 5, name: "Bruce")

Browser.drive {
    go "http://myapp.com/"
    to PersonPage, newPerson
}

This will result in a request to “http://myapp.com/person/5”.

5.6.1 Named params

Any type of argument can be used with the to() method, except named parameters (i.e. a Map). Named parameters are always interpreted as query parameters. Using the classes from the above example…

Browser.driver {
    go "http://myapp.com/"
    to PersonPage, newPerson, flag: true
}

This will result in a request to “http://myapp.com/person/5?flag=true”. The query parameters are not sent to the convertToPath() method.

5.7 Inheritance

Pages can be arranged in an inheritance hierarchy. The content definitions are merged…

class ExamplePage extends Page {
    static content = {
        heading { $("h1") }
    }
}

class SpecialExamplePage extends ExamplePage {
    static content = {
        footer { $("div.footer") }
    }
}

Browser.drive {
    to SpecialExamplePage
    assert heading.text() == "Special Example"
    assert footer.text() == "This is the footer"
}

If a subclass defines a content template with the same name as a content template defined in a superclass, the subclass version replaces the version from the superclass.

5.8 Lifecycle Hooks

Page classes can optionally implement methods that are called when the page is set as the browser’s current page and when it is swapped out for another page. This can be used to transfer state between pages.

5.8.1 onLoad(Page previousPage)

The onLoad() method is called with previous page object instance when the page becomes the new page object for a browser.

import geb.*

class SomePage extends Page {
    void onLoad(Page previousPage) {
        // do some stuff with the previous page
    }
}

5.8.2 onUnload(Page newPage)

The onUnload() method is called with next page object instance when the page is being replaced as the page object for the browser.

import geb.*

class SomePage extends Page {
    void onUnload(Page newPage) {
        // do some stuff with the new page
    }
}

5.9 Dealing with frames

Frames might seem a thing of the past but if you’re accessing or testing some legacy application with Geb you might still need to deal with them. Thankfully Geb makes working with them groovier thanks to the withFrame() method which is available on Browser, Page and Module.

5.9.1 Executing code in the context of a frame

There are multiple flavours of the withFrame() method, but the for all of them the closure parameter is executed in the context of a frame specified by the first parameter and after the execution the browser page is restored to what it was before the call:

Given the following html…

<html>
    <body>
        <frame name="header" src="frame.html"></frame>
        <frame id="footer" src="frame.html"></frame>
        <iframe id="inline" src="frame.html"></iframe>
        <span>main</span>
    <body>
</html>

…and the code for frame.html…

<html>
    <body>
        <span>frame text</span>
    </body>
</html>

…then this code will pass…

static content = {
    footerFrame { $('#footer') }
}

withFrame('header') { assert $('span') == 'frame text' }
withFrame('footer') { assert $('span') == 'frame text' }
withFrame(0) { assert $('span') == 'frame text' }
withFrame($('#footer')) { assert $('span') == 'frame text' }
withFrame(footerFrame) { assert $('span') == 'frame text' }

assert $('span') == 'main'

If a frame cannot be found for a given first argument of the withFrame() call then NoSuchFrameException is thrown.

5.9.2 Switching pages and frames at once

All of the aforementioned withFrame() variants also accept an optional second argument (a page class) which allows to switch page for the execution of the closure passed as the last parameter.

Following shows an example usage:

to PageWithFrames
//browser.page set to a PageWithFrames instance

withFrame('frame-name', PageDescribingFrameContents) {
    //browser.page set to a PageDescribingFrameContents instance
}

//browser.page set back to the PageWithFrames instance

It is also possible to specify a page to switch to for a page content that describes a frame.

6 Modules

Modules are re-usable definitions of content that can be used across multiple pages. They are useful for modelling things like UI widgets that are used across multiple pages, or even for defining more complex UI elements in the one page.

They are defined in a manner similar to pages, but extend Module

class ExampleModule extends Module {
    static content = {
        button { $("input", type: "submit") }
    }
}

Pages can “include” modules using the following syntax…

class ExamplePage extends Page {
    static content = {
        theModule { module ExampleModule }
    }
}

The module method is a special method only available in content template definitions. It sets the content to an instance of the module…

Browser.drive {
    to ExamplePage
    theModule.button.click()
}

Modules can also be parameterised…

class ExampleModule extends Module {
    def buttonName
    static content = {
        button { $("input", type: "submit", name: buttonName) }
    }
}

Where the parameters are set using the module method…

class ExamplePage extends Page {
    static content = {
        theModule { name -> module ExampleModule, buttonName: name }
    }
}

Browser.drive {
    to ExamplePage
    theModule("something").button.click()
}

Modules can also include other modules…

class ExampleModule extends Module {
    static content = {
        innerModule { module InnerModule }
    }
}

class InnerModule extends Module {
    static content = {
        button { $("input", type: "submit") }
    }
}

class ExamplePage extends Page {
    static content = {
        theModule { module ExampleModule }
    }
}

Browser.drive {
    theModule.innerModule.button.click()
}

6.1 Base And Context

Modules can be localised to a specific section of the page that they are used in, or they can specify an absolute context as part of their definition. There are two ways that a modules base/context can be defined.

It can be defined at inclusion time…

static content = {
    form { module FormModule, $("form") }
}

We can define a Navigator context when including the module using the above syntax. This now means that all $() function calls that occur within the module are against the given context (in this case, the form element).

However, module classes can also define their own base…

import geb.Module

class FormModule extends Module {
    static base = { $("form") }
}

This has the same effect as the code above.

They can also be combined. Consider the following HTML…

<div class="a">
    <form>
        <input name="thing" value="a"/>
    </form>
</div>
<div class="b">
    <form>
        <input name="thing" value="b"/>
    </form>
</div>

And the following content definitions…

import geb.*

class ExamplePage extends Page {
    static content = {
        formA { module FormModule, $("div.a") }
        formB { module FormModule, $("div.b") }
    }
}

class FormModule extends Module {
    static base = { $("form") }
    static content = {
        thingValue { thing().value() }
    }
}

When working with a browser at a ExamplePage page…

assert formA.thingValue == "a"
assert formB.thingValue == "b"

If the module declares a base, it is always calculated relative to the base given by the including statement. If the including statement does not specify a base, the module’s base is calculated relative to the including page’s base.

6.2 Reusing modules across pages

As previously stated, modules can be used to model page fragments that are reused across multiple pages. For example, many different types of pages in your application may show information about the user’s shopping cart. You could handle this with modules…

class CartInfoModule extends Module {
    static content = {
        section { $("div.cart-info") }
        itemCount { section.find("span.item-count").toInteger() }
        totalCost { section.find("span.total-cost").toDouble() }
    }
}

class HomePage extends Page {
    static content = {
        cartInfo { module CartInfoModule }
    }
}

class OtherPage extends Page {
    static content = {
        cartInfo { module CartInfoModule }
    }
}

Modules work well for this.

6.3 Using modules for repeating content on a page

Other than content that is repeated on different pages (like the shopping cart mentioned above), pages also have content that is repeated on the page itself. On a checkout page, the contents of the shopping cart could be summarized with the product name, the quantity and price for each product contained. For this kind of page, a list of modules can be collected using the moduleList function.

Consider the following HTML for our cart contents:

<table>
    <tr>
        <th>Product</th><th>Quantity</th><th>Price</th>
    </tr>
    <tr>
        <td>The Book Of Geb</td><td>1</td><td>5.99</td>
    </tr>
    <tr>
        <td>Geb Single-User License</td><td>1</td><td>99.99</td>
    </tr>
    <tr>
        <td>Geb Multi-User License</td><td>1</td><td>199.99</td>
    </tr>
</table>

We can model one line of the table like this:

class CartRow extends Module {
    static content = {
        cell { $("td", it) }
        productName { cell(0).text() }
        quantity { cell(1).text().toInteger() }
        price { cell(2).text().toDouble() }
    }
}

And define a list of CartRows in our Page:

class CheckoutPage extends Page {
    static content = {
        cartItems { moduleList CartRow, $("table tr").tail() } // tailing to skip the header row
    }
}

Because the return value of cartItems is a list of CartRow instances, we can use any of the usual collection methods:

assert cartItems.every { it.price > 0.0 }

We can also access the cart items like this:

assert cartItems[0].productName == "The Book Of Geb"

Unfortunatelly this has a perfromance penalty of creating all modules in the list. You can get around it and add support for ranges by changing your content definition to:

class CheckoutPage extends Page {
    static content = {
       cartItems { index -> moduleList CartRow, $("table tr").tail(), index }
    }
}

Now the all of the following will pass and is more efficient:

assert cartItems.every { it.price > 0.0 }
assert cartItems(0).productName == "The Book Of Geb"
assert cartItems(1..2)*.productName == ["Geb Single-User License", "Geb Multi-User License"]

Keep in mind that you can also pass module parameters the same way as you would with the module() method:

static content = {
    myContent { index -> moduleList MyModule, $(".myModuleClass"), index, myParam: 'param value' }
}

6.4 The Content DSL

The Content DSL used for modules is exactly the same as the one used for pages, so all of the same options and techniques can be used.

6.5 Inheritance

Modules can use inheritance in the same way that pages can. That is, their content definitions are merged with any content redefined in the subclass taking precedence of the superclass.

6.6 Size and Location

You can obtain the size and location of the module. All units are in pixels. The size is available via the height and width properties, while the location is available as the x and y properties which represent the distance from the top left of the page (or parent frame) to the top left point of the base of the module.

$("div").height == 20
$("div").width == 40
$("div").x == 60
$("div").y == 80

7 Configuration

Geb provides a configuration mechanism that allows you to control various aspects of Geb in a flexible way. At the heart of this is the Configuration object, which the Browser and other objects query at runtime.

There are three general mechanisms for influencing configuration; system properties, config script and the build adapter.

7.1 Mechanisms

7.1.1 The Config Script

Geb attempts to load a ConfigSlurper script named GebConfig.groovy from the default package (in other words, in the root of a directory that is on the classpath). If it is not found, Geb will try to load a ConfigSlurper class named GebConfig from the default package - this is usefull if you run tests that use Geb from an IDE because you won’t have to specify GebConfig.groovy as a resource, Geb will simply fall back to the compiled version of the script. If both script and class are not found Geb will continue using all defaults.

First, the script is looked for with the executing thread’s context class loader and if it is not found, then it is looked for with the class loader that loaded Geb. This covers 99% of scenarios out of the box perfectly well without any intervention. If however you do need to configure the context class loader to load the config script, you must make sure that it is either the same as the class loader that loaded Geb or a child of it. If the script is not found by both of those class loaders the procedure will be repeated but this time the class will be searched for - first using executing thread’s context class loader and then using the class loader that loaded Geb.

In a Grails project, the test/functional directory is a good place to put your config script in. If you are using a build tool such as Gradle or Maven that has the concept of test “resources”, then that directory is a suitable place. You can also put your script together with your compilation source and then the compiled version of the script will be used.

7.1.1.1 Environment Sensitivity

The Groovy ConfigSlurper mechanism has built in support for environment sensitive configuration, and Geb leverages this by using the geb.env system property to determine the environment to use. An effective use of this mechanism is to configure different drivers based on the designated Geb “environment” (concrete details on how to do this further down).

How you set the environment system property is going to be dependent on the build system you are using. For example, when using Grails you could control the Geb environment by specifying it on the command line…

grails -Dgeb.env=windows test-app functional:

Other build environments will allow you to do this in different ways.

7.1.2 System Properties

Some config options can be specified by system properties. In general, config options specified by system properties will override values set in the config script. See the config options below for which options are controllable via system properties.

7.1.3 Build Adapter

The build adapter mechanism exists to allow Geb to integrate with development/build environments that logically dictate config options. For example, Grails dictates what the base url and directory for reports should be set to and the Geb plugin for Grails uses the build adapter mechanism to set this up.

This mechanism works by loading the name of the class (fully qualified) by the system property geb.build.adapter that must implement the BuildAdapter interface. Currently, the build adapter can only influence the base url to use, and the location of the reports directory.

If the geb.build.adapter system property is not explicitly set, it defaults to SystemPropertiesBuildAdapter. As you can probably deduce, this default implementation uses system properties to specify values, so is usable in most circumstances. See the linked API doc for the details of the specific system properties it looks for.

Note that while the default build adapter uses system properties, it should not be considered to be the same as system property configuration due to values in the config script taking precedence over the build adapter which is not true for system properties.

7.2 Config Options

7.2.1 Driver Implementation

The driver to use is specified by the config key driver, or the system property geb.driver.

7.2.1.1 Factory Closure

In the config script it can be a closure that when invoked with no arguments returns an instance of WebDriver

import org.openqa.selenium.firefox.FirefoxDriver

driver = { new FirefoxDriver() }

This is the preferred mechanism, as it allows the most control over the drivers creation and configuration.

You can use the ConfigSlurper mechanism’s environment sensitivity to configure different drivers per environment …

import org.openqa.selenium.firefox.FirefoxDriver

import org.openqa.selenium.remote.DesiredCapabilities
import org.openqa.selenium.remote.RemoteWebDriver

// default is to use firefox
driver = { new FirefoxDriver() }

environments {
    // when system property 'geb.env' is set to 'win-ie' use a remote IE driver
    'win-ie' {
        driver = {
            new RemoteWebDriver(new URL("http://windows.ci-server.local"), DesiredCapabilities.internetExplorer())
        }
    }
}

WebDriver has the ability to drive browsers on a remote host, which is what we are using above. For more information consult the WebDriver documentation on remote clients and remote servers.

7.2.1.2 Driver Class Name

The name of the driver class to use (it will be constructed with no arguments) can be specified as a string with the key driver in the config script or via the geb.driver system property (the class must implement the WebDriver API).

driver = "org.openqa.selenium.firefox.FirefoxDriver"

Or it can be one of the following short names; ie, htmlunit, firefox or chrome. These will be implicitly expanded to their fully qualified class names …

driver = "firefox"

The following table gives the possible short names that can be used:

Short NameDriver
htmlunit org.openqa.selenium.htmlunit.HtmlUnitDriver
firefox org.openqa.selenium.firefox.FirefoxDriver
ie org.openqa.selenium.ie.InternetExplorerDriver
chrome org.openqa.selenium.chrome.ChromeDriver

If no explicit driver is specified then Geb will look for the following drivers on the classpath in the order they are listed in the above table. If none of these classes can be found, a UnableToLoadAnyDriversException will be thrown.

7.2.2 Navigator Factory

It is possible to specify your own implementation of NavigatorFactory via configuration. This is useful if you want to extend the Navigator class to provide your own behaviour extensions.

Rather than inject your own NavigatorFactory, it is simpler to inject a custom InnerNavigatorFactory which is a much simpler interface. To do this, you can specify a closure for the config key innerNavigatorFactory

innerNavigatorFactory = { Browser browser, List<org.openqa.selenium.WebElement> elements
    elements ? new MyCustomNavigator(browser, elements) : new geb.navigator.EmptyNavigator()
}

This is a rather advanced use case. If you need to do this, check out the source code or get in touch via the mailing list if you need help.

7.2.3 Driver Caching

Geb’s ability to cache a driver and re-use it for the lifetime of the JVM (i.e. the implicit driver lifecycle) can be disabled by setting the cacheDriver config option to false. However, if you do this you become responsible for quitting every driver that is created at the appropriate time.

The default caching behavior is to cache the driver globally across the JVM. If you are using Geb in multiple threads this may not be what you want as neither Geb Browser objects nor WebDriver at the core is thread safe. To remedy this, you can instruct Geb to cache the driver instance per thread by setting the config option cacheDriverPerThread to true.

Also, by default Geb will register a shutdown hook to quit any cached browsers when the JVM exits. You can disable this by setting te config property quitCachedDriverOnShutdown to false.

7.2.4 Base URL

The base URL to be used can be specified by setting the baseUrl config property (with a String) value or via the build adapter (the default implementation of which looks at the geb.build.baseUrl system property). Any value set in the config script will take precedence over the value provided by the build adapter.

7.2.5 Waiting

The waitFor() methods available on browser, page and module objects can be affected by configuration (this is also true for implicitly waiting content). It is possible to specify default values for the timeout and retry interval, and to define presets of these values to be referred to by name.

7.2.5.1 Defaults

Defaults can be specified via:

waiting {
    timeout = 10
    retryInterval = 0.5
}

Both values are optional and in seconds. If unspecified, the values of 5 for timeout and 0.1 for retryInterval.

7.2.5.2 Presets

Presets can be specified via:

waiting {
    presets {
        slow {
            timeout = 20
            retryInterval = 1
        }
        quick {
            timeout = 1
        }
    }
}

Here we have defined two presets, slow and quick. Notice that the quick preset does not specify a retryInterval value; defaults will be substituted in for any missing values (i.e. giving the quick preset the default retryInterval value of 0.1).

7.2.6 Waiting in “at” checkers

At checkers can be configured to be implictly wrapped with waitFor calls. This can be set with:

atCheckWaiting = true

The possible values for the atCheckWaiting option are consistent with the ones for content definition and can be one of the following:

7.2.7 Unexpected pages

The unexpectedPages option allows to specify a list of unexpected Page classes that will be checked for when ”at“ checks are performed. Given that PageNotFoundPage and InternalServerErrorPage have been defined:

unexpectedPages = [PageNotFoundPage, InternalServerErrorPage]

See this section for more information.

7.2.8 Reporter

The reporter is the object responsible for snapshotting the state of the browser (see the reporting chapter for details). All reporters are implemenations of the Reporter interface. If no reporter is explicitly defined, a composite reporter will be created from a ScreenshotReporter (takes a PNG screenshot) and PageSourceReporter (dumps the current DOM state as HTML). This is a sensible default, but should you wish to use a custom reporter you can assign it to the reporter config key.

reporter = new CustomReporter()

7.2.9 Reports Dir

The reports dir configuration is used by to control where the browser should write reports (see the reporting chapter for details).

In the config script, you can set the path to the directory to use for reports via the reportsDir key…

reportsDir = "target/geb-reports"

The value is interpreted as a path, and if not absolute will be relative to the JVM’s working directory.

The reports dir can also be specified by the build adapter (the default implementation of which looks at the geb.build.reportsDir system property). Any value set in the config script will take precedence over the value provided by the build adapter.

It is also possible to set the reportsDir config item to a file.

reportsDir = new File("target/geb-reports")

By default this value is not set. The browser’s report() method requires a value for this config item so if you are using the reporting features you must set a reports dir.

7.2.10 Report Test Failures Only

By default Geb will take a report at the end of each test method, regardless of whether it ended successfully or not. The reportOnTestFailureOnly setting allows you to specify that a report should be taken only if a failure occurs. This might be useful as a way to speed up large test suites.

reportOnTestFailureOnly = true

Currently this flag is only supported by the TestNG adapter. Support for JUnit, Spock and other frameworks is forthcoming.

7.2.11 Reporting listener

It is possible to specify a listener that will be notified when reports are taken. See the section on listening to reporting for details.

7.2.12 Auto Clearing Cookies

Certain integrations will automatically clear the driver’s cookies, which is usually necessary when using an implicit driver. This configuration flag, which is true by default, can be disabled by setting the autoClearCookies value in the config to false.

autoClearCookies = false

7.3 Runtime Overrides

The Configuration object also has setters for all of the config properties it exposes, allowing you to override config properties at runtime in particular circumstances if you need to.

For example, you may have one Spock spec that requires the autoClearCookies property to be disabled. You could disable it for just this spec by doing something like…

import geb.spock.GebReportingSpec

class FunctionalSpec extends GebReportingSpec {
    def setup() {
       browser.config.autoClearCookies = false
    }
}

8 Implicit Assertions

As of Geb 0.7.0, certain parts of Geb utilise “implicit assertions”. This sole goal of this feature is to provide more informative error messages. Put simply it means that for a given block of code, all expressions are automatically turned into assertions. So the following code:

1 == 1

Becomes…

assert 1 == 1

If you’ve used the Spock Framework you will be well familiar with the concept of implicit assertions from Spock’s then: blocks.

In Geb, waiting expressions and at expressions automatically use implicit assertions. Take the following page object…

class ImplicitAssertionsExamplePage extends Page {

    static at = { title == "Implicit Assertions!" }

    static content = {
        dynamicParagraph(wait: true) { $("p", 0).text() == "implicit assertions are cool!" }
    }

    def waitForHeading() {
        waitFor { $("h1") }
    }
}

This automatically becomes…

class ImplicitAssertionsExamplePage extends Page {

    static at = { assert title == "Implicit Assertions!" }

    static content = {
        dynamicParagraph(wait: true) { assert $("p", 0).text() == "implicit assertions are cool!" }
    }

    def waitForHeading() {
        waitFor { assert $("h1") }
    }
}

Because of this, Geb is able to provide much better error messages when the expression fails due to Groovy’s power asserts.

Note: A special form of assert is used that returns the value of the expression, whereas a regular assert returns null.

This means that given…

static content = {
    headingText(wait: true) { $("h1").text() }
}

Accessing headingText here will wait for there to be a h1 and for it to have some text (because an empty string is false in Groovy), which will then be returned. This means that even when implicit assertions are used, the value is still returned and is is usable.

8.1 At Verification

Let’s take the at case.

If you’re unfamiliar with Geb’s “at checking”, please read this section.

Consider the following small Geb script…

Browser.drive {
    go ImplicitAssertionsExamplePage
    at ImplicitAssertionsExamplePage
}

At checking works by verifying that the page’s “at check” returns a trueish value. If it does, the at() method returns true. If not, the at() method will return false. However, due to implicit assertions, the “at check” will never return false. Instead, the at checker will throw an AssertionError. Because the page’s “at check” is turned into an assertion, we’ll get an error like:

Assertion failed: 

title == "Implicit Assertions!"
|     |
|     false
Something else

    at ImplicitAssertionsExamplePage._clinit__closure1(ImplicitAssertionsExamplePage.groovy:3)
    at ImplicitAssertionsExamplePage._clinit__closure1(ImplicitAssertionsExamplePage.groovy)
    at geb.Page.verifyAt(Page.groovy:131)
    at geb.Browser.doAt(Browser.groovy:335)
    at geb.Browser.at(Browser.groovy:259)
    at groovyscript(groovyscript.groovy:3)

As you can see, this is much more informative than the at() method simply returning false.

8.2 Waiting

Another place where implicit assertions are utilised is for waiting.

If you’re unfamiliar with Geb’s “waiting” support, please read this section.

Consider the following Geb script:

Browser.drive {
    waitFor { title == "Page Title" }
}

The waitFor method verifies that the given clause returns a trueish value within a certain timeframe. Because of implicit assertions, when this fails the error will look something like this:

geb.waiting.WaitTimeoutException: condition did not pass in 5.0 seconds (failed with exception)
    at geb.waiting.Wait.waitFor(Wait.groovy:128)
    at geb.waiting.WaitingSupport.doWaitFor(WaitingSupport.groovy:108)
    at geb.waiting.WaitingSupport.waitFor(WaitingSupport.groovy:84)
    at geb.waiting.WaitingSupport.waitFor(WaitingSupport.groovy:80)
    at geb.Browser.methodMissing(Browser.groovy:168)
    at geb.test.GebSpec.methodMissing(GebSpec.groovy:80)
    at groovyscript(groovyscript.groovy:2)
Caused by: Assertion failed: 

title == "Page Title"
|     |
|     false
Something else

    at geb.waiting.WaitingSupportSpec.failed waiting_closure4(WaitingSupportSpec.groovy:67)
    at geb.waiting.WaitingSupportSpec.failed waiting_closure4(WaitingSupportSpec.groovy)
    at geb.waiting.Wait.waitFor(Wait.groovy:117)
    ... 6 more

The failed assertion is carried as the cause of the geb.waiting.WaitTimeoutException and gives you an informative message as to why the waiting failed.

8.2.1 Waiting Content

The same implicit assertion semantics apply to content definitions that are waiting.

If you’re unfamiliar with Geb’s “waiting content” support, please read this section.

Any content definitions that declare a wait parameter have implicit assertions added to each expression just like waitFor() method calls.

8.3 How it works

The “implicit assertions” feature is implemented as a Groovy compile time transformation, which literally turns all expressions in a candidate block of code into assertions.

This transform is packaged as a separate jar named geb-implicit-assertions. This jar needs to be on the compilation classpath of your Geb test/pages/modules (and any other code that you want to use implicit assertions) in order for this feature to work.

If you are obtaining Geb via a dependency management system, this is typically not something you need to be concerned about as it will happen automatically. Geb is distributed via the Maven Central repository in Apache Maven format (i.e. via POM files). The main Geb module, geb-core depends on the geb-implicit-assertions module as a compile dependency.

If your dependency management system inherits transitive compile dependencies (i.e. also makes compile dependencies of first class compile dependencies first class compile dependencies) then you will automatically have the geb-implicit-assertions module as a compile dependency and everything will work fine (Maven, Gradle, Grails, and most configurations of Ivy do this). If your dependency management system does not do this, or if you are manually managing the geb-core dependency, be sure to include the geb-implicit-assertions dependency as a compile dependency.

9 Javascript, AJAX and Dynamic Pages

This section discusses how to deal with some of the challenges in testing and/or automating modern web applications.

9.1 The “js” object

The browser instance exposes a “js” object that provides support for working with Javascript over and above what WebDriver provides.
It’s important to understand how WebDriver does handle Javascript, which is through a driver’s implementation of JavascriptExecutor’s [executeScript()](http://selenium.googlecode.com/svn/trunk/docs/api/java/org/openqa/selenium/JavascriptExecutor.html#executeScript(java.lang.String, java.lang.Object[])) method.

Before reading further, it’s strongly recommended to read the description of [executeScript()](http://selenium.googlecode.com/svn/trunk/docs/api/java/org/openqa/selenium/JavascriptExecutor.html#executeScript(java.lang.String, java.lang.Object[])) in order to understand how type conversion works between the two worlds.

You can execute Javascript like you would with straight WebDriver using the driver instance via the browser…

assert browser.driver.executeScript("return arguments[0];", 1) == 1

This is a bit long winded, and as you would expect Geb uses the dynamism of Groovy to make life easier.

The JavascriptExecutor interface does not define any contract in regards to the driver’s responsibility when there is some issue executing Javascript. All drivers however throw some kind of exception when this happens.

9.1.1 Accessing Variables

Any global javascript variables inside the browser can be read as properties of the js object.

Given the following page…

<html>
    <script type="text/javascript">
        var aVariable = 1;
    </script>
<body>
</body>
</html>

We could access the javascript variable “aVariable” with…

Browser.drive {
    assert js.aVariable == 1
}

Or if we wanted to map it to page content…

class ExamplePage extends Page {
    static content = {
        aVar { js.aVariable }
    }
}

Browser.drive {
    to ExamplePage
    assert aVar == 1
}

We can even access nested variables…

assert js."document.title" == "Book of Geb"

9.1.2 Calling Methods

Any global javascript functions can be called as methods on the js object.

Given the following page…

<html>
    <script type="text/javascript">
        function addThem(a,b) {
            return a + b;
        }
    </script>
<body>
</body>
</html>

We can call the addThem() function with…

Browser.drive {
    assert js.addThem(1, 2) == 3
}

This also works from pages and modules.

To call nested methods, we use the same syntax as properties…

Browser.drive {
    js."document.write"("dynamic!")
}

9.1.3 Executing Arbitrary Code

The js object also has an exec() method that can be used to run snippets of Javascript. It is identical to the [JavascriptExecutor.executeScript()](http://selenium.googlecode.com/svn/trunk/docs/api/java/org/openqa/selenium/JavascriptExecutor.html#executeScript(java.lang.String, java.lang.Object[])) method, except that it takes its arguments in the other order…

assert js.exec(1, 2, "return arguments[0] + arguments[1];") == 3

You might be wondering why the order has been changed (i.e. the arguments go before the script). It makes writing multiline javascript more convenient…

js.exec 1, 2, """
    someJsMethod();
    // lots of javascript
    return true;
"""

9.2 Waiting

Geb provides some convenient methods for waiting for a certain condition to be true. This is useful for testing pages using AJAX, timers or effects.

The waitFor methods are provided by the WaitingSupport mixin which delegates to the Wait class (see the documentation of the waitFor method of this class for the precise semantics of waiting). These methods take various parameters that determine how long to wait for the given closure to return a true object according to the Groovy Truth, and how long to wait in between invoking the closure again.

waitFor {} // use default configuration
waitFor(10) {} // wait for up to 10 seconds, using the default retry interval
waitFor(10, 0.5) {} // wait for up to 10 seconds, waiting half a second in between retries
waitFor("quick") {} // use the preset “quick” as the wait settings

See the section on wait configuration for how to change the default values and define presets.

It is also possible to declare that content should be implicitly waited on, see the wait option for content definition.

9.2.1 Examples

Here is an example showing one way of using waitFor() to deal with the situation where clicking a button invokes an AJAX request that creates a new div on its completion.

import geb.*

class DynamicPage extends Page {
    static content = {
        theButton { $("input", value: "Make Request") }
        theResultDiv(required: false) { $("div#result") }
    }

    def makeRequest() {
        theButton.click()
        waitFor { theResultDiv.present }
    }
}

Browser.drive {
    to DynamicPage
    makeRequest()
    assert theResultDiv.text() == "The Result"
}

Notice that the ‘theResultDiv’ is declared required: false. This is almost always necessary when dealing with dynamic content as it’s likely to not be present on the page when it is first accessed (see: section on required)

Because the browser delegates method calls to the page object, the above could have been written as…

Browser.drive {
    $("input", value: "Make Request")
    waitFor { $("div#result").present }
    assert $("div#result").text() == "The Result"
}

Recall that the return keyword is optional in Groovy, so in the example above the $("div#result").present statement acts as the return value for the closure and is used as the basis on whether the closure passed or not. This means that you must ensure that the last statement inside the closure returns a value that is true according to the Groovy Truth (if you’re unfamiliar with the Groovy Truth do read that page).

Not using explicit return statements in closure expressions passed to waitFor() is actually preferred. See the section on implicit assertions for more information.

The closures given to the waitFor method(s) do not need to be single statement.

waitFor {
    def a = 1
    def b = 2
    a == b
}

That will work fine.

If you wish to test multiple conditions as separate statement inside a waitFor closure, you can just put them in separate lines.

waitFor {
    1 == 1
    2 == 2
}

9.2.2 Custom message

If you wish to add a custom message to WaitTimeoutException that is being thrown when waitFor call times out you can do so by providing a message parameter to the waitFor call:

waitFor (message: 'My custom message') { $("div#result").present }

9.3 Alert and Confirm Dialogs

WebDriver currently does not handle the alert() and confirm() dialog windows. However, we can fake it through some Javascript magic as discussed on the WebDriver issue for this. Geb implements a workaround based on this solution for you. Note that this feature relies on making changes to the browser’s window DOM object so may not work on all browsers on all platforms. At the time when WebDriver adds support for this functionality the underlying implementation of the following methods will change to use that which will presumably be more robust. Geb adds this functionality through the AlertAndConfirmSupport class that is mixed into
Page and
Module.

The Geb methods prevent the browser from actually displaying the dialog, which is a good thing. This prevents the browser blocking while the dialog is displayed and causing your test to hang indefinitely.

Unexpected alert() and confirm() calls can have strange results. This is due to the nature of how Geb handles this internally. If you are seeing strange results, you may want to run your tests/scripts against a real browser and watch what happens to make sure there aren’t alert()s or confirm()s being called that you aren’t expecting. To do this, you need to disable Geb’s handling by changing your code to not use the methods below.

9.3.1 alert()

There are two methods that deal with alert() dialogs:

String withAlert(Closure actions)
void withNoAlert(Closure actions)

The first method, withAlert(), is used to verify actions that will produce an alert() dialog. This method returns the alert message.

Given the following HTML…

<input type="button" name="showAlert" onclick="alert('Bang!');" />

The withAlert() method is used like so…

assert withAlert { $("input", name: "showAlert").click() } == "Bang!"

If an alert dialog is not raised by the given “actions” closure, an AssertionError will be thrown.

The withAlert() method also accepts a wait option. It is useful if the code in your “actions” closure is raising a dialog in an asynchronous manner and can be used like that:

assert withAlert(wait: true) { $("input", name: "showAlert").click() } == "Bang!"

The value for the wait option can be one of the following:

Any other value will be interpreted as false.

The second method, withNoAlert(), is used to verify actions that will not produce an alert() dialog. If an alert dialog is raised by the given “actions” closure, an AssertionError will be thrown.

Given the following HTML…

<input type="button" name="dontShowAlert" />

The withNoAlert() method is used like so…

withNoAlert { $("input", name: "dontShowAlert").click() }

It’s a good idea to use withNoAlert() when doing something that might raise an alert. If you don’t, the browser is going to raise a real alert dialog and sit there waiting for someone to click it which means your test is going to hang. Using withNoAlert() prevents this.

A side effect of the way that this is implemented is that we aren’t able to definitively handle actions that cause the browser’s actual page to change (e.g. clicking a link in the closure given to withAlert()/withNoAlert()). We can detect that the browser page did change, but we can’t know if alert() did or did not get called before the page change. If a page change was detected the withAlert() method will return a literal true (whereas it would normally return the alert message), while the withNoAlert() will succeed.

9.3.2 confirm()

There are three methods that deal with confirm() dialogs:

String withConfirm(boolean ok, Closure actions)
String withConfirm(Closure actions) // defaults 'ok' to true
void withNoConfirm(Closure actions)

The first method, withConfirm() (and its ‘ok’ defaulted relative), is used to verify actions that will produce an confirm() dialog. This method returns the confirmation message. The ok parameter controls whether the confirm() call should return true or false (i.e. the user has clicked the “OK” or “Cancel” buttons).

Given the following HTML…

<input type="button" name="showConfirm" onclick="confirm('Do you like Geb?');" />

The withConfirm() method is used like so…

assert withConfirm(true) { $("input", name: "showConfirm").click() } == "Do you like Geb?"

If a confirmation dialog is not raised by the given “actions” closure, an AssertionError will be thrown.

The withConfirm() method also accepts a wait option just like the withAlert() method. See the description of withAlert() to learn about the possible values and usage.

The second method, withNoConfirm(), is used to verify actions that will not produce an confirm() dialog. If a confirmation dialog is raised by the given “actions” closure, an AssertionError will be thrown.

Given the following HTML…

<input type="button" name="dontShowConfirm" />

The withNoConfirm() method is used like so…

withNoConfirm { $("input", name: "dontShowConfirm").click() }

It’s a good idea to use withNoConfirm() when doing something that might raise a a confirmation. If you don’t, the browser is going to raise a real confirmation dialog and sit there waiting for someone to click it which means your test is going to hang. Using withNoConfirm() prevents this.

A side effect of the way that this is implemented is that we aren’t able to definitively handle actions that cause the browser’s actual page to change (e.g. clicking a link in the closure given to withConfirm()/withNoConfirm()). We can detect that the browser page did change, but we can’t know if confirm() did or did not get called before the page change. If a page change was detected the withConfirm() method will return a literal true (whereas it would normally return the alert message), while the withNoConfirm() will succeed.

9.3.3 About prompt()

Geb does not provide any support for prompt() due to it’s infrequent and generally discouraged use.

9.4 jQuery Integration

Geb has special support for the jQuery javascript library. Navigator objects have a special adapter that makes calling jQuery methods against the underlying DOM elements simple. This is best explained by example.

The jQuery integration only works when the pages you are working with include jQuery, Geb does not install it in the page for you. The minimum supported version of jQuery is 1.4.

Consider the following page:

<html>
<head>
    <script type="text/javascript" src="/js/jquery-1.4.2.min.js"></script>
    <script type="text/javascript">
        $(function() {
            $("#a").mouseover(function() {
               $("b").show(); 
            });
        });
    </script>
</head>
<body>
    <div id="a"></div>
    <div id="b" style="display:none;"><a href="http://www.gebish.org">Geb!</a></div>
</body>
</html>

We want to click the Geb link, but can’t because it’s hidden (WebDriver does not let you interact with hidden elements). The div containing the link (div “a”) is only displayed when the mouse moves over div “a”.

The jQuery library provides convenient methods for triggering browser events. We can use this to simulate the mouse being moved over the div “a”.

In straight jQuery JavaScript we would do…

jQuery("div#a").mouseover()

Which we could invoke via Geb easy enough…

js.exec 'jQuery("div#a").mouseover();'

That will work, but can be inconvenient as it duplicates content definitions in our Geb pages. Geb’s jQuery integration allows you to use your defined content in Geb with jQuery. Here is how we could call the mouseover jQuery function on an element from Geb…

$("div#a").jquery.mouseover()

To be clear, that is Groovy (not JavaScript code). It can be used with pages…

import geb.*

class ExamplePage extends Page {
    static content = {
        divA { $("#a") }
        divB { $("#b") }
        gebLink { divB.find("a") }
    }
}

Browser.drive {
    to ExamplePage
    // div b is not showing
    divA.jquery.mouseover()
    // div b is showing now
    gebLink.click()
}

The jquery property of a navigator is conceptually equivalent to a jQuery object for all of the navigator’s matched page elements.

The methods can also take arguments…

$("#a").jquery.trigger('mouseover')

The same set of restricted types as allowed by WebDriver’s [executeScript()](http://selenium.googlecode.com/svn/trunk/docs/api/java/org/openqa/selenium/JavascriptExecutor.html#executeScript(java.lang.String, java.lang.Object[])) method are permitted here.

The return value of methods called on the jquery property depends on what the corresponding jQuery method returns. A jQuery object will be converted to a Navigator representing the same set of elements, other values such as strings and numbers are returned as per WebDriver’s [executeScript()](http://selenium.googlecode.com/svn/trunk/docs/api/java/org/openqa/selenium/JavascriptExecutor.html#executeScript(java.lang.String, java.lang.Object[])) method.

9.4.1 Why?

This functionality was developed to make triggering mouse related events easier. Some applications are very sensitive to mouse events and triggering these events in an automated environment is a challenge. jQuery provides a good API for faking these events which makes for a good solution.

10 Direct Downloading

Geb features an API that can be used to make direct HTTP requests from the application that is executing the Geb scripts or tests. This facilitates fine grained requests and downloading content such as PDF, CSVs, images etc. into your scripts or tests to then do something with.

The direct download API works by using java.net.HttpURLConnection to directly connect to a URL from the application executing Geb, bypassing WebDriver.

The Direct Download API is provided by the DownloadSupport class, which is mixed in to pages and modules (which means you can just call these instance methods directly from anywhere where you would want to, e.g. drive blocks, in tests/specs, methods on page objects, methods on modules). Consult the DownloadSupport API reference for the download* methods that are available.

10.1 Downloading Example

For example, let’s say you are using Geb to exercise a web application that generates PDF documents. The WebDriver API can only deal with HTML documents. You want to hit the PDF download link and also do some tests on the downloaded PDF. The direct download API is there to fill this need.

Browser.drive {
    go "http://myapp.com/login"

    // login
    username = "me"
    password = "secret"
    login().click()

    // now find the pdf download link
    def downloadLink = $("a.pdf-download-link")

    // now get the pdf bytes
    def bytes = downloadBytes(downloadLink.@href)
}

Simple enough, but consider what is happening behind the scenes. Our application required us to log in, which implies some kind of session state. Geb is using HttpURLConnection behind the scenes to get the content and before doing so the cookies from the real browser will be transferred allowing this connection to assume the same session. The PDF download link href may also be relative and Geb handles this by resolving the link passed to the download function against the browser’s current page URL.

10.2 Fine Grained Request

The Direct Download API can also be used for making fine grained requests which can be useful for testing edge cases or abnormal behavior.

All of the download*() methods take an optional closure that can configure the java.net.HttpURLConnection that will be used to make the request (after the Cookie header has been set).

For example, we could test what happens when we send gibberish in the Accept-Encoding header.

Browser.drive {
    go "http://myapp.com/somepage"
    downloadText { HttpURLConnection connection ->
        connection.setRequestProperty("Accept-Encoding", "gibberish")
    }
}

Before doing something like the above, it’s worth considering whether doing such testing via Geb (a browser automation tool) is the right thing to do. You may find that it’s more appropriate to directly use HttpURLConnection without Geb. That said, there are scenarios where such fine grained request control can be useful.

10.3 Dealing with untrusted certificates

When facing web applications using untrusted (e.g. self-signed) SSL certificates, you will likely get exceptions when trying to use Geb’s download API. By overriding the behavior of the request you can get around this kind of problem. Using the following code will allow running requests agains a server which uses a certificate from the given keystore:

import geb.download.helper.SelfSignedCertificateHelper
downloadText { HttpURLConnection connection ->
    if (connection instanceof HttpsURLConnection) {
        def helper = new SelfSignedCertificateHelper(getClass().getResource('/keystore.jks'), 'changeit')
        helper.acceptCertificatesFor(connection as HttpsURLConnection)
    }
}

10.4 Default Configuration

In the configuration, the default behaviour of the HttpURLConnection object can be specified by providing a closure as the defaultDownloadConfig property.

defaultDownloadConfig = { HttpURLConnection connection ->
    // configure the connection
}

This config closure will be run first, so anything set here can be overriden using the fine grained request configuration shown above.

10.5 Errors

Any IO type errors that occur during a download operation (e.g. HTTP 500 responses) will result in a DownloadException being thrown that wraps the original exception and provides access to the HttpURLConnection used to make the request.

11 Scripts and Binding

Geb supports being used in scripting environments via both the Browser.drive() method, and by using the geb.binding.BindingUpdater class that populates and updates a groovy.lang.Binding that can be used with scripts. This is also the same mechanism that is used by the EasyB Geb plugin and can be used with Cuke4Duke (Cucumber for the JVM).

11.1 Setup

To use the binding support, you simply create a BindingUpdater object with a Binding and Browser

import geb.Browser
import geb.binding.BindingUpdater
import groovy.lang.Binding

def binding = new Binding()
def browser = new Browser()
def updater = new BindingUpdater(binding, browser)

// populate and start updating the browser
updater.initialize()

// Run a script from the filesystem
new GroovyShell(binding).evaluate(new File("someScript.groovy"))

// remove Geb bits from the binding and stop updating it
updater.remove()

11.2 The binding environment

11.2.1 Browser methods & properties

The BindingUpdater installs shortcuts into the binding for most of the browser object’s public methods.

For example…

go "some/page"
assert at(SomePage)
waitFor { $("p.status").text() == "ready" }
js.someJavaScriptFunction()
downloadText($("a.csvFile"))

In a managed binding, all of the methods/properties that you can usually call in the Browser.drive() method are available. This includes the $() function.

The following methods are available:

The javascript interface property js is also available. The browser object itself is available as the browser property.

11.2.2 The current page

The binding updater also updates the page property of the binding to be the browser’s current page…

import geb.Page

class SomePage extends Page {
    static content = {
       button(to: OtherPage) { $("input.do-stuff") }
    }
}

to SomePage
assert page instanceof SomePage
page.button.click()
assert page instanceof OtherPage

12 Reporting

Geb includes a simple reporting mechanism which can be used to snapshot the state of the browser at any point in time. Reporters are implementations of the Reporter interface. Geb ships with two implementations; PageSourceReporter and ScreenshotAndPageSourceReporter. There are three bits of configuration that pertain to reporting; the reporter implementation, the reports directory and whether to only report test failures or not.

If no reporter is explicitly defined, a composite reporter will be created from a ScreenshotReporter (takes a PNG screenshot) and PageSourceReporter (dumps the current DOM state as HTML).

You take a report by calling the report(String label) method on the browser object.

Browser.drive {
    go "http://google.com"
    report "google home page"
}

The report() method will thrown an exception if it is called and there is no configured reportsDir. If you are going to use reporting you must specify a reportsDir via config.

Assuming that we configured a reportsDir of “reports/geb”, after running this script we will find two files in this directory:

To avoid issues with reserved characters in filenames, Geb replaces any character in the report name that is not an alphanumeric, a space or a hyphen with an underscore.

12.1 The report group

The configuration mechanism allows you to specify the base reportsDir which is where reports are written to by default. It is also possible to change the report group to a relative path inside this directory.

Browser.drive {
    reportGroup "google"
    go "http://google.com"
    report "home page"

    reportGroup "wikipedia"
    go "http://wikipedia.org"
    report "home page"
}

We have now created the following files inside the reportsDir

The browser will create the directory for the report group as needed. By default, the report group is not set which means that reports are written to the base of the reportsDir. To go back to this after setting a report group, simply call reportGroup(null).

It is common for test integrations to manage the report group for you, setting it to the name of the test class.

12.2 Listening to reporting

It is possible to register a listener on the reporter that gets notified when a report is taken. This was added to make it possible to write something to stdout when a report is taken, which is how the Jenkins JUnit Attachments Plugin makes it possible to associate arbitrary files to test execution. Reporting listeners are of type ReportingListener can be specified as part of the config…

reportingListener = new ReportingListener() {
  void onReport(Reporter reporter, ReportState reportState, List<File> reportFiles) {
    reportFiles.each {
      println "[[ATTACHMENT|$it.absolutePath]]"
    }
  }
}

12.3 Cleaning

Geb does not automatically clean the reports dir for you. It does however provide a method that you can call to do this.

Browser.drive {
    cleanReportGroupDir()
    go "http://google.com"
    report "home page"
}

The cleanReportGroupDir() method will remove whatever the reports group dir is set to at the time. If it cannot do this it will throw an exception.

The Spock, JUnit and TestNG test integrations do automatically clean the reports dir for you, see the section in the testing chapter on these integrations.

13 Testing

Geb provides first class support for functional web testing via integration with popular testing frameworks such as Spock, JUnit, TestNG, EasyB and Cuke4Duke.

13.1 Spock, JUnit & TestNG

The Spock, JUnit and TestNG integrations work fundamentally the same way. They provide subclasses that setup a browser instance that all method calls and property accesses/references resolve against via Groovy’s methodMissing and propertyMissing mechanism.

Recall that the browser instance also forwards any method calls or property accesses/references that it can’t handle to it’s current page object, which helps to remove a lot of noise from the test.

Consider the following Spock spec…

import geb.spock.GebSpec

class FunctionalSpec extends GebSpec {
    def "go to login"() {
        when:
        go "/login"

        then:
        title == "Login Screen"
    }
}

Which is equivalent to…

import geb.spock.GebSpec

class FunctionalSpec extends GebSpec {
    def "go to login"() {
        when:
        browser.go "/login"

        then:
        browser.page.title == "Login Screen"
    }
}

13.1.1 Configuration

The browser instance is created by the testing integration. The configuration mechanism allows you to control aspects such as the driver implementation and base url.

13.1.2 Reporting

The Spock, JUnit and TestNG integrations also ship a superclass (the name of the class for each integration module is provided below) that automatically takes reports at the end of test methods with the label “end”. They also set the report group to the name of the test class (substituting “.” for “/”).

The report(String label) browser method is replaced with a specialised version. This method works the same as the browser method, but adds counters and the current test method name as prefixes to the given label.

package my.tests

import geb.spock.GebReportingSpec

class FunctionalSpec extends GebReporting {

    def "login"() {
        when:
        go "login"
        username = "me"
        report "login screen" // take a report of the login screen
        login().click()

        then:
        title == "Logged in!"
    }
}

Assuming a configured reportsDir of reports/geb and the default reporter (i.e. ScreenshotAndPageSourceReporter), we would find the following files:

The report file name format is:

«test method number»-«report number in test method»-«test method name»-«label».«extension»

Reporting is an extremely useful feature and can help you diagnose test failures much easier. Wherever possible, favour the use of the auto reporting base classes.

13.1.3 Cookie management

The Spock, JUnit and TestNG integrations will automatically clear the browser’s cookies at the end of each test method. For JUnit 3 this happens in the tearDown() method in geb.junit3.GebTest, for JUnit 4 it happens in an @After method in geb.junit4.GebTest and for TestNG it happens in an @AfterMethod method in geb.testng.GebTest.

The geb.spock.GebSpec class will clear the cookies in the cleanup() method unless the spec is @Stepwise, in which case they are cleared in cleanupSpec() (meaning that all feature methods in a stepwise spec share the same browser state).

This auto clearing of cookies can be disabled via configuration.

13.1.4 Jar and class names

The following table illustrates the specific jars and class names for Spock and JUnit.

Framework JAR Base Class Reporting Base Class
Spock geb-spock geb.spock.GebSpec geb.spock.GebReportingSpec
JUnit 4 geb-junit4 geb.junit4.GebTest geb.junit4.GebReportingTest
JUnit 3 geb-junit3 geb.junit3.GebTest geb.junit3.GebReportingTest
TestNG geb-testng geb.testng.GebTest geb.testng.GebReportingTest

13.1.5 Example Projects

The following projects can be used as starting references:

13.2 EasyB

Geb’s EasyB support is based around the binding management features discussed earlier.
EasyB stories are implemented as Groovy scripts with a binding,
and the Geb plugin simply integrates Geb’s BindingUpdater into the EasyB lifecycle.

The Geb EasyB plugin is currently under-developed and is in need of some attention. If you’d like to see better EasyB integration consider getting involved in its development.

Here’s a quick example…

using "geb" // EasyB syntax for using plugins

scenario "using geb", {
    given "our base url", {
        baseUrl = "http://my.app"
    }

    when "we go to the page", {
        to SomePage
    }

    then "we arrive at the page", {
        at SomePage
    }

    and "can use the javascript interface", {
        js.someJsVariable.shouldBe 1
    }

    and "can do some waiting", {
        waitFor { $("p").text() == "done" }
    }

    and "can work with the page", {
        page.div.text().shouldBe "d1"
    }
}

class SomePage extends geb.Page {
    static content = {
        div { $("#d1") }
    }
}

13.2.1 Configuration

Configuration is done in the given block of a scenario or story. Here you can optionally set 3 properties; driver, baseUrl and browser.

You can set the driver property to the driver instance that you want to implicitly created browser instance to use. However, using the configuration mechanism for driver implementation is preferred.

You can set the baseUrl property to the base url that you want to implicitly created browser instance to use. However, using the configuration mechanism for base url is preferred.

For fine grained control, you can create your own browser instance and assign it to the browser property. Otherwise, an implicit browser object is created using driver and/or baseUrl if they were explicitly set (otherwise the configuration mechanism is used.)

13.3 Cucumber (Cuke4Duke)

Geb doesn’t offer any explicit integration with Cuke4Duke but due to Cuke4Duke’s use of Groovy scripts, Geb’s binding management features can be used to great effect.

The following is an example of what is possible…

import static org.junit.Assert.*
import static org.junit.matchers.JUnitMatchers.*

import pages.*

this.metaClass.mixin(cuke4duke.GroovyDsl)

Given(~"I am on the Google search page") { ->
    to GoogleHomePage
    waitFor { at GoogleHomePage }
}

When(~"I search for \"(.*)\"") { String query ->
    page.searchField.value(query)
    page.searchButton.click()
}

Then(~"I am at the results page") {
    assert at(GoogleResultsPage)
}

Then(~"The first link should be \"(.*)\"") { String text ->
    waitFor { page.results }
    assertThat page.resultLink(0).text(), containsString(text)
}

13.3.1 Example Projects

The following projects can be used as starting references:

14 SauceLabs Integration

Geb comes with some tools that make configuring your Geb code to run in SauceLabs easier.

14.1 SauceLabsDriverFactory

First, there is the SauceLabsDriverFactory that given a browser specification as well as an username and access key creates an instance of RemoteWebDriver configured to use a browser in SauceLabs cloud. One would typically use it in GebConfig.groovy like this:

def sauceBrowser = System.getProperty("geb.sauce.browser")
if (sauceBrowser) {
    driver = {
       def username = System.getenv("GEB_SAUCE_LABS_USER")
       assert username
       def accessKey = System.getenv("GEB_SAUCE_LABS_ACCESS_PASSWORD")
       assert accessKey
       new SauceLabsDriverFactory().create(sauceBrowser, username, accessKey)
    }
}

This will configure Geb to run in SauceLabs if geb.sauce.browser system property is set and if not it will use whatever driver that is configured - this is useful if you want to run the code in local browser for development. In theory you could use any system property to pass the browser specification but geb.sauce.browser is also used by Gradle geb-saucelabs plugin so it’s a good idea to stick with that property name. The example also uses two environment variables to pass the username and access key to the factory as it’s usually the easiest way of passing something secret to your build in open CI services like drone.io or Travis CI if your code is public, but you could use any other mechanism if desired.

The first parameter passed to the create() method is a ”browser specification“ and it should have the following format:

«browser»:«operating system»:«version»

Assuming you’re using the snippet from the example above in your GebConfig.groovy to execute your code with Firefox 19 on Linux you would set the geb.sauce.browser system property to firefox:linux:19 and to execute it with IE 9 on Vista to internetExplorer:vista:9. Some browsers like Chrome automatically update to the latest version - for these browsers you don’t need to specify the version as there’s only one and you would use something like chrome:mac as the ”browser specification“. For a full list of available browsers, versions and operating systems refer to the SauceLabs platform list. Please note that Gradle geb-saucelabs plugin can set the geb.sauce.browser system property for you using the aforementioned format.

14.2 Gradle geb-saucelabs Plugin

The second part of SauceLabs integration is the geb-saucelabs Gradle plugin. You can use it in your Gradle build to easily create multiple Test tasks that will have geb.sauce.browser property set. The value of that property can be then passed in configuration file to SauceLabsDriverFactory as the ”browser specification“. What follows is a typical usage example:

apply plugin: "geb-saucelabs" //1

buildscript { //2
    repositories {
       mavenCentral()
    }
    dependencies {
       classpath 'org.gebish:geb-gradle:0.9.2'
    }
}

repositories { //3
    maven { url "http://repository-saucelabs.forge.cloudbees.com/release" }
}

dependencies { //4
    sauceConnect "com.saucelabs:sauce-connect:3.0.28"
}

sauceLabs {
    browsers { //5
       firefox_linux_19
       chrome_mac
       internetExplorer_vista_9
    }
    task { //6
       testClassesDir = test.testClassesDir
       testSrcDirs = test.testSrcDirs
       classpath = test.classpath
    }
    account { //7
       username = System.getenv(SauceAccount.USER_ENV_VAR)
       accessKey = System.getenv(SauceAccount.ACCESS_KEY_ENV_VAR)
    }
}

In (1) we apply the plugin to the build and in (2) we’re specifying how to resolve the plugin. In (3) and (4) we’re defining dependencies for the sauceConnect configuration - this will be used by tasks that open a SauceConnect tunnel before running the generated test tasks which means that the browsers in the cloud will have localhost pointing at the machine running the build. In (5) we’re saying that we want our tests to run in 3 different browsers - this will generate the following Test tasks: firefoxLinux19Test, chromeMacTest and internetExplorerVista9Test. You can use allSauceTests task that will depend on all of the generated test tasks to run all of them during a build. The configuration closure specified at (6) is used to configure all of the generated test tasks - for each of them the closure is run with delegate set to a test task being configured. Finally in (7) we pass credentials for SauceConnect.

15 Build System & Framework Integrations

This kind of integration for Geb is typically focussed on managing the base url and reports dir as build systems tend to be able to provide this configuration (via the build adapter mechanism).

15.1 Grails

Grails is a popular web app framework. There is a grails-geb plugin available that allows you to use Geb for your Grails functional tests. This plugin simply manages the baseUrl and reportsDir configuration items.

You still need to include the appropriate Geb module for testing (i.e. geb-junit4, geb-spock or geb-easyb) yourself. You may also need to depend on other plugins like the grails-spock plugin to enable those kinds of tests.

For example, if you plan to use Spock with Geb you would need to add the following to the BuildConfig.groovy

dependencies {
    test "org.gebish:geb-spock:0.9.2"
}
plugins {
    test ":spock:0.7"
    test ":geb:0.9.2"
}

Where 0.9.2 and 0.7 are the versions of Geb and Spock you wish to use.

As Grails provides JUnit support out of the box, you only need to pull in the geb-junit4 jar to use Geb with JUnit.

Grails 1.3 and later use JUnit 4. Earlier versions of Grails than this use Groovy 1.6 which Geb no longer supports.

dependencies {
    test "org.gebish:geb-junit4:0.9.2"
}
plugins {
    test ":geb:0.9.2"
}

You only need the appropriate Geb test integration jar, as it will depend on geb-core and Grails’ dependency management will take care of getting that for you.

You will also of course need a driver and the selenium-support dependency, which you can also specify in BuildConfig.groovy.

dependencies {
    test "org.seleniumhq.selenium:selenium-support:«webdriver version»"
    test "org.seleniumhq.selenium:selenium-firefox-driver:«webdriver version»"
}

HTMLUnit depends on some XML processing libraries that cause issues with Grails. You can avoid this by excluding certain dependencies of the HTMLUnit driver…

test("org.seleniumhq.selenium:selenium-htmlunit-driver:«webdriver version»") {
    exclude 'xml-apis'
}

Recall that Geb looks for its configuration file as GebConfig.groovy on the classpath. A good location for this file is in a Grails project is the test/functional directory is on the classpath at test time. You do not need to set the baseUrl and reportsDir config entries in a Grails project as the plugin takes care of those for you. The baseUrl is set to the root of the Grails application, and the reportsDir is set to geb inside Grails’ test reports dir (which by default is target/test-reports).

There is nothing special about writing Geb tests with Grails. You subclass the same classes as usual (e.g. geb.spock.GebReportingSpec for Spock tests).

There is an example project available that uses geb-junit4 and geb-spock to test some Grails scaffold pages.

15.2 Gradle

Using Geb with Gradle simply involves pulling in the appropriate dependencies, and configuring the base URL and reports dir in the build script if they are necessary.

Below is a valid Gradle build.gradle file for working with Geb for testing.

apply plugin: "groovy"

repositories {
    mavenCentral()
}

configurations {
    testCompile.transitive = true
}

dependencies {
    groovy "org.codehaus.groovy:groovy-all:1.8.9"

    def gebVersion = "0.9.2"
    def seleniumVersion = "2.26.0"

    // If using Spock, need to depend on geb-spock
    testCompile "org.gebish:geb-spock:0.9.2"
    testCompile "org.spockframework:spock-core:0.7-groovy-1.8"

    // If using JUnit, need to depend on geb-junit (3 or 4)
    testCompile "org.gebish:geb-junit4:0.9.2"
    testCompile "junit:junit-dep:4.8.2"

    // Need a driver implementation
    testCompile "org.seleniumhq.selenium:selenium-firefox-driver:2.26.0"
    testRuntime "org.seleniumhq.selenium:selenium-support:2.26.0"
}

test {
    systemProperties "geb.build.reportsDir": "$reportsDir/geb"
}

There is a Gradle example project available.

15.3 Maven

Using Geb with Maven simply involves pulling in the appropriate dependencies, and configuring the base URL and reports dir in the build script if they are necessary.

Below is a valid pom.xml file for working with Geb for testing (with Spock).

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.gebish.example</groupId>
  <artifactId>geb-maven-example</artifactId>
  <packaging>jar</packaging>
  <version>1</version>
  <name>Geb Maven Example</name>
  <url>http://www.gebish.org</url>
  <dependencies>
    <dependency>
      <groupId>org.codehaus.groovy</groupId>
      <artifactId>groovy-all</artifactId>
      <version>1.7.10</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.spockframework</groupId>
      <artifactId>spock-core</artifactId>
      <version>0.7-groovy-1.8</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.gebish</groupId>
      <artifactId>geb-spock</artifactId>
      <version>0.9.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-firefox-driver</artifactId>
      <version>2.26.0</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-support</artifactId>
      <version>2.26.0</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.9</version>
        <configuration>
          <includes>
            <include>*Spec.*</include>
          </includes>
          <systemPropertyVariables>
            <geb.build.baseUrl>http://google.com/ncr</geb.build.baseUrl>
            <geb.build.reportsDir>target/test-reports/geb</geb.build.reportsDir>
          </systemPropertyVariables>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.codehaus.gmaven</groupId>
        <artifactId>gmaven-plugin</artifactId>
        <version>1.3</version>
        <configuration>
          <providerSelection>1.7</providerSelection>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

There is a Maven example project available.

16 IDE Support

Geb does not require any special plugins or configuration for use inside an IDE. However, there are some considerations that will be addressed in this chapter.

16.1 Execution

Geb scripts can be executed in an IDE if that IDE supports executing Groovy scripts. All IDEs that support Groovy typically support this. There are typically only two concerns in the configuration of this; getting the Geb classes on the classpath, and the GebConfig file.

Geb tests can be executed in an IDE if that IDE supports Groovy scripts and the testing framework that you are using with Geb. If you are using JUnit or Spock (which is based on JUnit) this is trivial, as all modern Java IDEs support JUnit. As far as the IDE is concerned, the Geb test is simply a JUnit test and no special support is required. As with executing scripts, the IDE must put the Geb classes on the classpath for test execution and the GebConfig file must be accessible (typically putting this file at the root of the test source tree is sufficient).

In both cases, the simplest way to create such an IDE configuration is to use a build tool (such as Gradle or Maven) that supports IDE integration. This will take care of the classpath setup and other concerns.

16.2 Authoring Assistance (autocomplete and navigation)

This section discusses what kind of authoring assistance can be provided by IDEs and usage patterns that enable better authoring support.

16.2.1 Dynamism and conciseness vs tooling support

Geb heavily embraces the dynamic typing offered by Groovy, to achieve conciseness for the purpose of readability. This immediately reduces the amount of authoring assistance that an IDE can provide when authoring Geb code. This is an intentional compromise. The primary cost in functional/acceptance testing is in the maintenance of the test suite over time. Geb optimizes for this in several ways, one of which being the focus on intention revealing code (which is achieved through conciseness).

That said, if authoring support is a concern for you, read on to learn for details on ways to forsake conciseness in order to improve authoring support.

16.2.2 Strong typing

In order to gain improved authoring support, you must include types in your tests and page objects. Additionally, you must explicitly access the browser and page objects instead of relying on dynamic dispatch.

Here’s an example of idiomatic (untyped) Geb code.

to HomePage
loginButton.click()

at LoginPage
username = "user1"
password = "password1"
loginButton.click()

at SecurePage

The same code written with types would look like:

HomePage homePage = browser.to HomePage
homePage.loginButton.click()

LoginPage loginPage = browser.at LoginPage
SecurePage securePage = loginPage.login("user1", "password1")

Where the page objects are:

class HomePage extends Page {
    Navigator getLoginButton() {
       $("#loginButton")
    }
}

class LoginPage extends Page {

    static at = { title == "Login Page" }

    SecurePage login(String username, String password) {
        $("#username").value username
        $("#password").value username
        $("#loginButton").click()
        browser.at SecurePage
    }
}

In summary:

  1. Use the browser object explicitly (made available by the testing adapters)
  2. Use the page instance returned by the to() and at() methods instead of calling through the browser
  3. Use methods on the Page classes instead of the content {} block and dynamic properties

Using this “typed” style is not an all or nothing proposition. The typing options exist on a spectrum and can be used selectively where/when the cost of the extra “noise” is worth it to achieve better IDE support. For example, a mix of using the content {} DSL and methods can easily be used. The key enabler is to capture the result of the to() and at() methods in order to access the page object instance.

16.2.2.1 IntelliJ IDEA support

IntelliJ IDEA (since version 12) has special support for authoring Geb code. This is built in to the Groovy support; no additional installations are required.

The support provides:

This effectively enables more authoring support with less explicit type information. The Geb development team would like to thank the good folks at JetBrains for adding this explicit support for Geb to IDEA.

18 About the Project

The Geb home page can be found at http://www.gebish.org.

18.1 API Reference

The API reference can be found here.

18.2 Support & Development

Support for Geb is offered on the user@geb.codehaus.org mailing list, which can be subscribed to here.

Ideas and new features for Geb can be discussed on the dev@geb.codehaus.org mailing list, which can be subscribed to here.

18.3 Credits

18.3.1 Committers

18.3.2 Contributors

18.4 History

This page lists the high level changes between versions of Geb.

18.4.1 0.9.2

18.4.1.1 New Features

18.4.1.2 Fixes

18.4.1.3 Breaking Changes

18.4.2 0.9.1

18.4.2.1 Breaking Changes

18.4.2.2 New Features

18.4.2.3 Fixes

18.4.3 0.9.0

18.4.3.1 New Features

18.4.3.2 Fixes

18.4.3.3 Breaking Changes

18.4.4 0.7.2

18.4.4.1 Fixes

18.4.5 0.7.1

18.4.5.1 New Features

18.4.5.2 Fixes

18.4.6 0.7.0

18.4.6.1 New Features

18.4.6.2 Breaking Changes

18.4.7 0.6.3

18.4.7.1 New Features

18.4.8 0.6.2

18.4.8.1 New Features

18.4.8.2 Breaking Changes

18.4.9 0.6.1

18.4.9.1 New Features

18.4.9.2 Breaking Changes

18.4.10 0.6

18.4.10.1 New Features

18.4.10.2 Breaking Changes

18.4.11 0.5.1

18.4.12 0.5

18.4.12.1 New Features

18.4.12.2 Breaking Changes

18.4.13 0.4

Initial Public Release

The Book of Geb - 0.9.2 - October, 2013
Licensed under the Apache License, Version 2.0