Geb and Grails – tips, tricks and gotchas

I’ve been getting familiar with Spock and Geb for the last few weeks. Here are some quick notes, links and files that I have compiled from this experience. I’ve gathered them here for my own quick reference.

Geb is Luke Daley’s fantastic new functional testing library based on web driver. To get started with it, you need to read the book of Geb.

Installing Geb and Spock – step by step.

1. Install the plugins

grails install-plugin geb
grails install-plugin spock 0.5- groovy-1.7-SNAPSHOT

2. Add test dependencies for web driver
In grails-app/conf/BuildConfig.groovy , add the following to the dependencies block:

dependencies {
       test 'org.seleniumhq.selenium:selenium-firefox-driver:latest.release'
       test 'org.seleniumhq.selenium:selenium-chrome-driver:latest.release'
       test 'org.seleniumhq.selenium:selenium-ie-driver:latest.release'
       test('org.seleniumhq.selenium:selenium-htmlunit-driver:latest.release') {
               exclude 'xml-apis'
       }
}

Remember to uncomment the mavenCentral repository for this to work.

3. Install Robert Fletcher’s artefact creation patch for Spock.

These files from Robert Fletcher’s blog replace the standard jUnit test cases that ship with grails with Spock specs. This means that when you run commands like ‘grails create-integration-test com.mydomain.is.awesome’, you get a Spock spec instead of a jUnit test.

  1. Copy Events.groovy to your scripts directory
  2. Call grails install-templates
  3. Copy Spec.groovy into src/templates/artifacts

( Note: the create scripts are broken in grails 1.3.4, so either use grails 1.3.3 or the latest trunk build of grails )

Use grails console for Geb

One of the challenges of learning Geb for me was trying to understand the jQuery-like syntax supported by the $() navigators.

A useful trick I found was to use grails console to quickly evaluate the results of my expressions. Once I was confident that the expression was right, I could then copy these into the content node of my page objects.

  • Call ‘grails console’ in your application root.
  • Call Browser.drive( ) and use println expressions to output your results. I use the following script:
import geb.*
import org.openqa.selenium.firefox.FirefoxDriver

Browser.drive( new FirefoxDriver(), "http://www.grails.org" ){
    go()
   // expression here
}

Testing Ajax Pages

Luke Daley provided the implementation of a waitFor method in the geb-user mailing list.

       def waitFor(Closure condition) {
               waitFor(null, condition)
       }

       def waitFor(Double timeoutSecs, Closure condition) {
               waitFor(timeoutSecs, null, condition)
       }

       def waitFor(Double timeoutSecs, Double intervalSecs, Closure condition) {
               timeoutSecs = timeoutSecs ?: 5
               intervalSecs = intervalSecs ?: 0.5
               def loops = Math.ceil(timeoutSecs / intervalSecs)
               def pass = condition()
               def i = 0
               while (pass == false && i++ < loops) {
                       Thread.sleep((intervalSecs * 1000) as long)
                       pass = condition()
               }
               if (i >= loops) {
                       throw new AssertionError("condition did not pass in $timeoutSecs seconds")
               }
               true
       }

waitFor basically loops until a certain condition is met. In my spock tests, I would call something like:

when: 'I click on search'
    searchButton.click()
then: 'search wait dialog appears'
    waitFor{ loadingDiv.present }
then: 'search wait dialog is gone'
    waitFor{ !loadingDiv.present }
then: 'my results are loaded'
   ...

I added these to a base page I called AjaxPage and have all my pages that use ajax extend from it.

It is important to put divs that are not visible at the beginning of the page load as required:false in page objects. i.e:

content{
    loadingDiv( required: false ){ $( '#loading' ) }
}

Executing JavaScript

Web driver cannot simulate mouse movement events. Web driver also requires all elements that you call to be visible on the page before you can interact with them or it will throw an exception. You can read more about this in the web driver faq.

The solution to this problem is to use the web driver JavascriptExecutor to put the page in the proper stage. I add this helper method to my Specs:

import org.openqa.selenium.JavascriptExecutor
def js( String script ){
     (driver as JavascriptExecutor).executeScript( script )
}

Then, in my tests, I can simply invoke commands like this:

js( ' jQuery( "myHiddenDiv" ).show(); ' )
js( ' return jQuery( "myHiddenDiv" ).isVisible(); ' )

Important: The reason this method is put in my specs instead of the base page object is because the later seems to make the calls non-blocking. I.e, if I put the js definition in my AjaxPage, waitFor{ myDiv.present } run right away instead of waiting for js( ‘ showHiddenDiv() ‘ ) to finish.

Geb Gotchas

Here are a few things that I always seem to keep forgetting:

1. Call go(). You need to call go() when using a driver other than htmlUnit due to a current bug in Geb 0.4. In my spock tests, it looks like this

when:
   to HomePage
   go()

and in the drive closure,

Browser.drive( new FirefoxDriver(), "http://www.fark.com/" ){
     go()
}

2. There are two GebSpecs, make sure you use the one for grails. There is a file called GebSpec in geb.spock.GebSpec and one for grails called grails.plugin.geb.GebSpec. You MUST make sure you have the right package name and have all your functional specs extend grails.plugin.geb.GebSpec or your test won’t run, your wife will leave you and you will leave a life of loneliness and tears.

3. Spock tests are called Specs. If you call then tests, they won’t get picked up in testing.

4. Remember to use require:false in your page objects. If your div is not visible on startup, you must set required to false – see testing Ajax Pages in this post.

I hope you have found this useful. Join the discussion at the geb-user mailing list if you run into any issues or have questions.

13 thoughts on “Geb and Grails – tips, tricks and gotchas

  1. Luke Daley

    Thanks for trying Geb!

    Rob Fletcher also put a lot of work into Geb btw.

    Working with JS will definitely be better in 0.5, I’ll also flesh out some of the docs to make some of the things you point out a bit clearer.

    Regarding the go() bug, it should only be needed when using the drive() method and specifying a driver along with a base url.

    Reply
  2. Peter Ledbrook

    Another current gotcha: if you use assertions in an ‘at’ implementation, you must return ‘true’ from the closure:

    static at = {
        assert title == "Login"
        assert $("h1", text: "Login").size() > 0
        return true
    }
    

    Also, if a navigator returns an empty set of results, it currently evaluates to ‘true’! Which is why you have to test the number of matched elements. Hopefully that will change.

    Reply
  3. deigote

    Nice tips!

    I found specially usefull the waitFor trick. I’m using it for my Grails projects by adding the following in my Bootstrap.init:
    Browser.metaClass.waitForElement { element, timeoutSecs = 10, intervalSecs = 1 ->
    def loopsToDo = Math.ceil(timeoutSecs / intervalSecs)
    def loopsDone = 0
    while ($(element).isEmpty() && ++loopsDone < loopsToDo) {
    Thread.sleep(1000 * intervalSecs)
    }
    if (loopsDone == loopsToDo) {
    throw new GebException("Unable to find ${element} after ${loopsDone} tries")
    }
    }

    This way, I can do stuff like
    Browser.drive('https://www.coolsite.com') {
    go '/somepath.html'
    waitForElement 'input#someInput'
    waitForElement 'input#someOtherInput', 15
    ...
    }

    Nice :)!

    Reply
  4. Pingback: Blog bookmarks 09/01/2010 « My Diigo bookmarks

  5. Sudheer

    spock 0.5- groovy-1.7-SNAPSHOT is not available to install .
    I tried to Run One Geb sample . But I am getting the fallowing Exception .

    Any Idea to resole this?

    groovy.lang.MissingMethodException: No signature of method: static geb.Browser.drive() is applicable for argument types: (org.openqa.selenium.ie.InternetExplorerDriver, java.lang.String, SampleGeb$_run_closure1) values: [org.openqa.selenium.ie.InternetExplorerDriver: Implement me!, http://www.grails.org, SampleGeb$_run_closure1@4a2dd4]
    Possible solutions: drive(java.lang.String, groovy.lang.Closure), drive(org.openqa.selenium.WebDriver, java.lang.String, groovy.lang.Closure), drive(groovy.lang.Closure), drive(java.lang.Class, groovy.lang.Closure), drive(org.openqa.selenium.WebDriver, groovy.lang.Closure), drive(java.lang.String, java.lang.Class, groovy.lang.Closure)

    Reply
  6. Jet

    Thanks Tomas,

    I followed this post together with your other post “Changing selenium driver versions for Geb + Grails”, and it works.

    On my way to Geb.

    Reply
  7. Brian

    I have had a hell of a time getting GEB up and running from scratch. A few guys where I work, got it set up and I use it and love it at work-i’ve written many tests and it’s my favorite acceptance testing framework. But trying to get GEB running on my own, from scratch, has been a true pain. I really didn’t want to use graddle, or grails or anything. I just wanted to create a geb project in Maven in STS or eclipse and start writing tests. I thought it would be easy. it’s so damn difficult. The GEB manual doesn’t help. it just shows dependancies required for maven/gradle/etc. I put them in, I get error after error. I’ve tried loading in “examples” out of the git repo that is often linked to. None of that works either (the only geb maven example there loads in with errors.) I’ve spent days on this, trying different solutions: Gradle for example, only to find a whole new set of problems and errors. I really want to figure it out, as I love using GEB at work. I just want to be able to set it up anywhere else. I’ll give this a shot. at this point it’s the only walk through I haven’t tried. I’d love to see an up-to-date walkthrough, using the latest eclipse, latest geb, latest spock… from nothing to a working test.

    Reply
  8. Pingback: Not recovering from an f5 drop during spock test

Leave a reply to Kirk Cancel reply