What is Geb?

Geb is a browser automation solution.

You can use it for…

It brings together the…

About the Project

Free Open Source, Apache License, Version 2.0.

Currently at version 0.6.3, preparing for 0.7.0 release soon.

Built by Gradle, naturally.

Project Components

The heart is the geb-core component which is all you really need (plus WebDriver).

For testing, you probably also want one of these as well:

(Has been used from Cuke4Duke as well).

There is also a Grails plugin.

WebDriver

Successor to the Selenium project.

Also known as “Selenium 2”.

Sponsored and driven by Google.

Becoming a standard.

http://dvcs.w3.org/hg/webdriver/raw-file/515b648d58ff/webdriver-spec.html

Cross-browser automation

Java based, with many language bindings.

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.By;
import org.openqa.selenium.firefox.FirefoxDriver;

WebDriver driver = new FirefoxDriver();
driver.get("http://google.com");
WebElement heading = driver.findElement(By.tagName("h1"));

Mobile Browsers

Rapidly improving.

Can use real devices or emulators in most cases.

A headless webkit based driver is in progress.

WebDriver API

Geb sits on top of WebDriver so you very rarely deal with its API, though it's accessible if you need it.

Geb never talks to the actual browser.

That's what WebDriver does.

Driver dependency

You need to pull in a specific driver implementation for each browser you want to work with.

<dependency>
  <groupId>org.seleniumhq.selenium</groupId>
  <artifactId>selenium-firefox-driver</artifactId>
  <version>2.20.0</version>
</dependency>

jQuery - write more, do less

jQuery provides an incredibly powerful API for navigating and selecting content.

$("div#footer").prev().childen();

CSS based, a whole lot better than XPath.

Geb's inspiration

Geb features a “Navigator API” that is inspired by jQuery.

// This is Geb code, not jQuery JavaScript…

$("h1").previous().children();

API is not identical.

Dynamic JVM Language

Groovy is…

Required Groovy Knowledge

You don't need to be a Groovy ninja to use Geb.

Groovy knowledge can definitely help when things go wrong though.

Geb & Groovy

Geb uses Groovy's dynamism to remove boilerplate.

import geb.*

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

* As of Geb 0.7.0 also uses Groovy's compile time transfomations.

Demo

Google for Wikipedia

Page Objects

The key to not pulling your hair out when dealing with web tests.

What are they?

In a phrase: Domain Modelling.

By modelling and creating abstractions, we can isolate implementation detail.

$("input[name=username]").value("user")
$("input[name=username]").value("password")
$("input[type=submit]").click()

Is far more fragile than this…

void login(String username, String password) {
    $("input[name=username]").value(username)
    $("input[name=username]").value(password)
    $("input[type=submit]").click()
}

login("user", "password")

Just good programming

It's the application of trusted principles; encapsulation and reuse.

Not new at all, but new to the world of web testing/automation.

Not just about modelling “pages”. It's about modelling all kinds of things in the domain of a user's actions online.

Just giving symbolic names to page content is a great start.

Browser has-a Page

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

The to() and click() methods are changing the underlying page.

You can refer to the current page's content and methods just by name.

Geb's Page Objects

Geb builds the Page Object pattern directly into the framework (though it is optional).

import geb.*

class GoogleHomePage extends Page {
    static url = "http://google.com/ncr"
    static at = { title == "Google" }
    static content = {
        search { module GoogleSearchModule }
    }
}

Geb's Page Objects

Features the “Content DSL” for naming content in a dynamic and powerful way.

import geb.*

class GoogleResultsPage extends Page {
    static at = { waitFor { title.endsWith("Google Search") } }
    static content = {
        search { module GoogleSearchModule }
        results { $("li.g") }
        result { i -> results[i] }
        resultLink { i -> result(i).find("a.l", 0) }
        firstResultLink { resultLink(0) }
    }
}

Geb's Page Objects

Very lightweight, minimum requirements are low.

import geb.*

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

Modules

Modules are repeating and/or reappearing content.

import geb.*

class GoogleSearchModule extends Module {
    static content = {
        field { $("input", name: "q") }
        button(to: GoogleResultsPage) { btnG() }
    }

    void forTerm(term) {
        field.value term
        waitFor { button.displayed }
        button.click()
    }
}

Modules

<table id="book-results">
  <thead>
    <tr>
      <th>Title</th><th>Author</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Zero History</td><td>William Gibson</td>
    </tr>
    <tr>
      <td>The Evolutionary Void</td><td>Peter F. Hamilton</td>
    </tr>
  </tbody>
</table>

Modules can be used for repeating content.

Modules

Modules have a base, from which all content lookups are relative.

class BooksPage extends Page {
  static content = {
    bookResults { 
      module BookRow, $("table#book-results tbody tr", it) 
    }
  }
}

class BookRow extends Module {
  static content = {
    cell { $("td", it) }
    title { cell(0).text() }
    author { cell(1).text() }
  }
}

Modules

We now have a model for a row in our table.

expect: 
bookResults(0).title == "Zero History" 
bookResults(1).author == "Peter F. Hamilton

Can be used for any reused/repeating content.

Note: talking about domain objects, not HTML tables and rows.

Inheritance

Pages (and modules) can be arranged in inheritance hierarchies.

class Footer extends Module {
    static content = {
        copyright { $("p.copyright") }
    }
}
class StandardPage extends Page {
    static content = {
        footer { module Footer }
    }
}
class FrontPage extends StandardPage {}

The front page will inherit the “footer” content definition.

Testing

Geb's testing adapters

Geb for Testing

Geb can be used with…

The majority of Geb users use Spock.

Geb can dump HTML and screenshots for each “test” to help in debugging.

Demo

Geb Testing

Navigator API

jQuery inspired content selection/navigation

The $() method

Returns a Navigator object.

General format:

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

Examples:

$("div") // all divs
$("div", 0) // first div
$("div", 0..2) // first three divs

// The third section heading with text “Geb”
$("h2", 2, id: "section", text: "Geb")

CSS Selectors

Full CSS3 if the target browser supports it.

$("div.some-class p:first[title='something']")
$("ul li a")
$("table tr:nth-child(2n+1) td")
$("div#content p:first-child::first-line")

CSS lookups are fast.

Attribute/Text matching

Can match on attribute values:

//<div foo="bar">
$("div", foo: "bar")

The “text” attribute is special:

//<div>foo</div>
$("div", text: "foo")

Can use Regular Expressions:

//<div>foo</div>
$("div", text: ~/f.+/)

Predicates

Geb supplies some handy predicates:

$("p", text: startsWith("p"))
$("p", class: contains("section"))
$("p", id: endsWith(~/\d/))

There are more of these.

Relative Content

$() returns a Navigator that allows you to find relative content.

$("p").previous()
$("p").prevAll()
$("p").next()
$("p").nextAll()
$("p").parent()
$("p").siblings()
$("div").children()

Most of these methods take selectors, indexes and attribute text/matchers too.

$("p").nextAll(".listing")

Content DSL

Reuse FTW

Content DSL

class GoogleResultsPage extends Page {
    static content = {
        results { $("li.g") }
        result { i -> results[i] }
        resultLink { i -> result(i).find("a.l", 0) }
        firstResultLink { resultLink(0) }
    }
}

Content definitions can build upon each other.

Content definitions are actually templates.

Optional Content

class OptionalPage extends Page {
    static content = {
        errorMsg(required: false) { $("p.errorMsg") }
    }
}

By default, Geb will error if the content you select doesn't exist.

The “required” option disables this check.

Dynamic Content

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

Geb will wait for some time for this content to appear.

By default, it will look for it every 100ms for 5s before giving up. This is highly configurable.

Same semantics as the waitFor {} method that can be used anywhere.

Navigation

Getting around

The to() method

class GoogleHomePage extends Page {
    static url = "http://google.com/ncr"
}

Pages can define a url that defines the page location.

The to() method sends the browser there and sets that as the current page object.

to GoogleHomePage

The page url can be relative (will be resolved against a config driven base).

There are advanced parameterisation options too.

Content based navigation

class FrontPage {
    static content = {
        aboutUsLink(to: AboutUsPage) { 
            $("div#nav ul li a", text: iStartsWith("About Us"))
        }
    }
}

When this content is clicked, the underlying page will be changed implicitly.

to FrontPage
aboutUsLink.click()
page instanceof AboutUsPage

Demo

Remote Browsers

What we didn't see

And more!

Slide Notes…