Geb Tips & Tricks

Craig Atkinson | Chief Technologist | Object Partners

About Me

  • Craig Atkinson
  • Chief Technologist, Object Partners (OPI)
  • Using Geb 4 past years
  • Very minor Geb contributor


  • Stable tests in CI environments
  • Alternative approach to page objects
  • Complex UI elements
  • Cross-browser testing
  • Running tests in parallel
  • Remote controls
  • Mock 3rd-party services

Stable tests in CI

  • Frustrating when tests pass locally but fail in CI
  • Failures tougher to debug

Timing issues

  • CI machines often slower than developer laptops
  • Dialogs that open or close slowly, dynamic content rendering, etc.
  • Geb has built-in waiting support

Geb waiting support

  • Wait for elements to be visible, page content to change, etc.
  • Can also wait for data changes (new DB record created, etc.)
  • waitFor method available in tests and page objects
  • Waits for any closure to return true

Waiting examples

  waitFor {
  assert $("div.alert").text() == "Error creating user"

  waitFor {
  loginPage.login('myUsername', 'myPassword')

Test failure screenshots

  • Usually can't watch browser test run on CI server
  • PNG screenshot and HTML dump when test finishes
  • Have tests extend GebReportingSpec
  • Configure CI build to archive screenshots & HTML

Screenshot configuration


  // Configure where screenshots are stored
  reportsDir = 'build/test-reports'
  // Optional, less noise when only failed tests generate screenshots
  reportOnTestFailureOnly = true

Javascript errors

  • Often have logs for server errors
  • More code on front-end with newer frameworks like Angular, React, etc.
  • What about Javascript errors?

Capture browser logs

  def cleanup() {
    LogEntries logs = driver.manage().logs().get(LogType.BROWSER)
    List errorLogEntries = logs.filter(Level.SEVERE)
    if (errorLogEntries) {
      println "Browser errors:"
      errorLogEntries.each { logEntry ->
        println("[${logEntry.level}] ${logEntry.message}")

Browser log example

  // Intentional Javascript error for demonstrating capturing browser logs

  Browser errors:
  [SEVERE] /author/edit/4 59:13 Uncaught ReferenceError: fakeObject is not defined

Browser logging gotchas

  • Not supported by Internet Explorer
  • For safety, wrap logging code in try/catch

Headless CI

Page Objects

Page Object pattern

  • Abstract page-specific details into helper classes (Page Objects)
  • Re-used across tests
  • Single point of maintenance
  • Tests become easier to read

Page Object Example

  class LoginPage extends geb.Page {
    static content = {
      usernameField { $("#username") }
      passwordField { $("#password") }
      submitButton(to: DashboardPage) { $("#submit") }

Using page objects in test

Standard: Geb delegates method calls to current page object

Test with standard page objects

  to HomePage
  username = "user1"
  password = "password1"
  // What page is the test on now?
  // What fields are on the current page?

Alternative approach to page objects

Strongly-typed page objects

  • Test uses instance of current page object
  • Page objects have methods that resemble user actions
  • Methods that change page return instance of new page
  • Tests even easier to read
  • IDE autocomplete available on pages in tests

Test using typed pages

  HomePage homePage = to(HomePage)
  LoginPage loginPage = homePage.clickLoginButton()
  DashboardPage dashboardPage = loginPage.login("user1", "password1")

Chain page calls

  HomePage homePage = to(HomePage)
  DashboardPage dashboardPage = homePage
    .login("user1", "password1")

Typed home page

  class HomePage extends geb.Page {
    static content = {
      loginButton(to: LoginPage) { $("#loginButton") }
    LoginPage clickLoginButton() {

Typed login page

  class LoginPage extends geb.Page {
    static content = {
      usernameField { $("#username") }
      passwordField { $("#password") }
      submitButton(to: DashboardPage) { $("#submit") }
    DashboardPage login(String username, String password) {

Typed page objects summary

  • Some additional work to write page objects
  • Save much more time when writing (and reading) test cases

Complex UI elements

Mouse interactions

Click and drag slider with mouse

  static content = {
    ratingSliderHandle { $(".ui-slider-handle") }
  void moveRatingSlider(Integer rating) {
    // Slider is 400 pixels wide and starts at 1,
    // so each notch above 1 is 100 pixels apart
    Integer numPixelsX = (rating - 1) * 100
    interact {
      moveByOffset(numPixelsX, 0)

Mouse interaction demo

Use keyboard

Send keystrokes with left-shift operator <<

  $("#myInputField") << "value"

  $(".ui-slider-handle") << Keys.ARROW_RIGHT

Keyboard demo

Execute raw Javascript

  • Last-ditch effort with complex UI controls
  • All Navigator elements have a 'jquery' field to run JS

Show hidden element

  static content = {
    hiddenLink { $("#hiddenLink") }
  void clickHiddenLink() {

Cross-Browser Testing

Available browsers

  • Any browser with a Selenium/Webdriver library
  • Real: Firefox, Chrome, IE, Safari, etc.
  • Simulated: HtmlUnit, PhantomJS

Configuring browser in Geb

  • Browser Selenium driver dependency
  • Additional OS-specific browser driver
  • Section in GebConfig.groovy
  • Pass environment parameter to Geb

Selenium driver dependencies

  def seleniumVersion = "2.53.1"
  testCompile "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}"
  testCompile "org.seleniumhq.selenium:selenium-firefox-driver:${seleniumVersion}"
  testCompile "org.seleniumhq.selenium:selenium-ie-driver:${seleniumVersion}"

Operating system driver

  • Chrome & Internet Explorer need local driver executable
  • Firefox <= 47 doesn't need one, Firefox >= 48 does
  • Manually download, install, and configure
  • Or ...

Automatic download

WebdriverManager dependency


Chrome GebConfig Example

  import io.github.bonigarcia.wdm.ChromeDriverManager
  environments {
    chrome {
      driver = { new ChromeDriver() }

WebDriverManager configuration

  • Drivers downloaded to ~/.m2/repository/webdriver by default
  • Download location and many other parameters are configurable via config file or system properties

Example config file

  # src/test/resources/

Pass geb.env parameter

Grails 2

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

Grails 3

  gradle -Dgeb.env=chrome integrationTest

Spring Boot

  gradle -Dgeb.env=chrome test

Tweak Grails 3 Gradle file

  // Pass system properties through to the integrationTest task
  // so we can pass in the 'geb.env' property
  configure(integrationTest) {

Tweak Spring Boot Gradle file

  // Pass system properties through to the test task
  // so we can pass in the 'geb.env' property
  configure(test) {

Parallel testing

Parallel testing requirements

  • Build tool that supports parallel testing (Gradle, etc.)
  • Tests can't modify any shared data
  • Safest to have each test set up its own data

Parallel testing example

  • Using Gradle
  • Start app using Grails wrapper
  • Run 2 tests simultaneously
  • Shut down Grails app
  • Demo

Groovy Remote Control

What are remote controls useful for?

  • When tests run in separate JVM from application
  • Setting up data specific to a test
  • Grabbing data after test for verification
  • Retrieving & setting up data in mock services (stay tuned)

How does it work?

Data setup example

  Idea createIdea(String title, String description) {
    RemoteControl remote = new RemoteControl()
    remote {
      Idea idea = new Idea(
          title: title,
          description: description

  List ideas = (1..5).collect { i ->
    ideaRemoteControl.createIdea("Title ${i}", "Description ${i}")

Data verification example

  Idea findByTitle(String title) {
    RemoteControl remote = new RemoteControl()
    remote {

Grails 3 injection

Tests and app run in same JVM

  import grails.test.mixin.integration.Integration
  import org.springframework.beans.factory.annotation.Autowired
  class AuthorGebSpec extends GebReportingSpec {
      AuthorDataUtil authorDataUtil
      void "should create Author"() {
        assert authorDataUtil.findByLastName('Last')?.firstName == 'First'

Mock third-party services

Proliferation of Third-party services

  • Email, payment processing, storage, address verification, etc.
  • Great for productivity, but can complicate testing
  • Don't want our tests dependent on services out of our control


  • Mock request/response from external services
  • Our tests not dependent on external service
  • Tests have tight control over service responses

Test-specific Dependency Injection

  • When using DI framework like Spring, Guice, etc.
  • Create mock version of code that calls external service
  • Use DI to replace code with mock version during functional tests
  • Setup and verify data from mock services in tests

Example service to mock

PatentService sends ideas to patent office

  class PatentService {
    def sendToPatentOffice(Idea idea) {
      // Send the idea to the real patent office

Mock Patent Service

  class PatentServiceMock extends PatentService {
    List ideasSentToPatentOffice = []
    def sendToPatentOffice(Idea idea) {
      ideasSentToPatentOffice << idea

Use mock patent service

  • Replace PatentService instance with mock version
  • In Grails, grails-app/conf/spring/resources.groovy

  beans = {
    if (Environment.current == Environment.TEST) {
      // Override the PatentService with our mock version when running tests

Test that hits mock service

  PatentServiceMock patentService
  def 'should submit idea to patent office'() {
    Idea idea = ideaRemoteControl.findByTitle('Patentable Idea')
    IdeaShowPage ideaShowPage = to([id:], IdeaShowPage)
    ideaShowPage = ideaShowPage.submitIdeaToPatentOffice()
    List ideasSubmittedToPatentOffice = patentService.ideasSentToPatentOffice
    assert ideasSubmittedToPatentOffice*.id.contains(

Wrapping up


  • Stable tests in CI environments
  • Alternative approach to page objects
  • Complex UI elements
  • Cross-browser testing
  • Running tests in parallel
  • Data access with remote controls
  • Mock 3rd-party services


Contributing to Geb


  • @craigatk1