Geb Tutorial

Geb Tutorial – Automated Browser Testing

In this example we’ll look at the Groovy alternative to automated browser testing (integration testing) using Geb. Geb is built on top of Selenium drivers and brings the tests into a very readable format. Depending on your QA resource they might just write the tests for you. Geb can work with JUnit or Spock (I’ll use Spock for this example), it can be in the main project or if there are a ton of tests then it can stand alone test project. Get ready to love page models and jQuery selector syntax!

Getting Ready

Application Setup
These tests are built on the Spring Data JPA example in a previous post. The full source code for the testing series is at the bottom of the page.

For this example we add the required dependencies to the pom.xml. This includes JUnit, Groovy, Spock, Geb and Selenium Drivers.

	<!--Testing-->
     <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.11</version>
         <scope>test</scope>
     </dependency>

     <!--Geb Integration Tests-->
     <dependency>
         <groupId>org.codehaus.groovy</groupId>
         <artifactId>groovy-all</artifactId>
         <version>2.1.5</version>
     </dependency>

	 <!--Spock support-->
     <dependency>
         <groupId>org.spockframework</groupId>
         <artifactId>spock-core</artifactId>
         <version>0.7-groovy-2.0</version>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>org.gebish</groupId>
         <artifactId>geb-spock</artifactId>
         <version>0.9.0</version>
         <scope>test</scope>
     </dependency>

	<!-- Selenium Drivers (all the desired browser drivers here) -->
     <dependency>
         <groupId>org.seleniumhq.selenium</groupId>
         <artifactId>selenium-firefox-driver</artifactId>
         <version>2.33.0</version>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>org.seleniumhq.selenium</groupId>
         <artifactId>selenium-support</artifactId>
         <version>2.33.0</version>
         <scope>test</scope>
     </dependency>

Geb Configuration

Create a groovy class in the test source package src/test/resources/GebConfig.groovy. Geb will automatically find this file by convention so name is important and it must be in the root class path. More details on the ConfigSlurper is available in the Geb manual.

import org.openqa.selenium.firefox.FirefoxDriver

driver = { new FirefoxDriver() }

baseUrl = "http://localhost:8080"

reportsDir = new File("target/geb-reports")
reportOnTestFailureOnly = true
  • driver is the default driver which is set here as FireFox.
  • baseUrl is the root URL that all tests will pull from. This is a convenience so the pages can be agnostic to the root domain which helps facilitate multi environment testing.
  • reportsDir is required and tells the test runner where to save the test results.
  • reportOnTestFailureOnly does what it says and is used here to shorten the reports to just the stuff we need to act on.
Multi Environment Support

The ConfigSlurper supports environment definitions. To enable an environment specify -Dgeb.env=test-ie

The Geb manual is very good and covers all the options. I point it our here to show it is possible and is useful even in simple setups to test multiple browsers.

	environments {	    
	    'test-ie' {
			baseUrl = "http://test.yourdomain.com"
	        driver = {
	            new RemoteWebDriver(new URL("http://test.yourdomain.com"), DesiredCapabilities.internetExplorer())
	        }
	    }
	    'test-firefox' {
			baseUrl = "http://test.yourdomain.com"
			driver = { new FireFoxDriver(); }
	    }
	}
	

Define Geb Page Objects

Geb allows you to use page objects to separate the what from the how. That means all the logic about the page layout lives in its own file. The test drives against that file and then you just assert that the stuff you care about is there. The big benefit is that any changes to the page can but updated once and not impact all the tests.

The first page we will setup is the signup form (show below)

Signup Form

Now we create the page object for the Signup Form:

class SignupPage extends Page
{
    static url = "/"
    static at = { title == "Sample App Sign-up Page" }
    static content = {
        heading { $("h1").text() }
        errorHeading { $(".alert-error h4").text() }
        signupForm { $("form[id=signupForm]") }
        emailField { $("input[name=email]") }
        lastNameField { $("input[name=lastName]") }
        firstNameField { $("input[name=firstName]") }
        submitButton(to: SignupResultPage) { $("button[type=submit]") }
    }
}

This class defines what is on our page and how to access it.

  • url is the location of the page. If you use a relative path then it will be appended to the BaseUrl in the GebConfig.
  • at is used to assert that the driver is on the correct page. Usually this is the title or something unique and static about the page.
  • content can be anything on the page that you want a simple accessor for. In this case we want the heading deigned by a simple “H1”, errors, the form fields and the submit button. If a content element is associated with an action (link, button, navigation…) then you can set a “to:” attribute that tells Geb to expect a new page when called.

There are other really cool features you can do here. Some of my favorite is caching, modules, timeouts and the groovy .collect option. The Geb manual has more details and I can post an advanced Geb Page Object if there is any interest.

Now it’s time for the Confirmation Page Object:

Signup Form

class SignupResultPage extends Page {

    static url = "/create"
    static at = { title == "Sample App Sign-up Result Page" }
    static content = {
        heading { $("h1").text() }
    }

}

All the static vars are the same. This page is at “/create” and has a unique title. The only content we need is the heading since there is nothing really in this page.

Create the Geb Tests

The pages are defined and it’s time to get testing!

Create the test class: src/test/groovy/com/luckyryan/sample/integration/test/SignupPageIT.groovy. I used the *IT.groovy file naming convention for the failSafe maven plugin. This is used later in the example and does not mean anything specific for Geb.

@Stepwise
class SignupPageIT extends GebReportingSpec {

    def "Signup Test Happy Path"() {

        given: "I'm at the sign up form"
        to SignupPage

        when: "I signup as a valid user"
        emailField = "geb@test.com"
        firstNameField = "firstname"
        lastNameField = "lastname"
        submitButton.click()

        then: "I'm at the result page "
        at SignupResultPage

    }
}

@Stepwise is a Spock annotation that will share the browser state and run the tests in order.

Tests are defined in Spock format. They start with a normal groovy closure “def”, then a descriptor of the test as the function name. The core of the test is given:,when:,then:. Each declaration takes a description of the action for reference if the test fails. It also gives an indication to what is happening in the test without needed to read the code.

The first test is the happy path.

  • given: … to SignupPage is a Geb command which directs the web driver to the page url declared in the SignupPage object.
  • when: … is where we put page interactions. In this case it fills out the form the clicks the submit button. Notice how there is no reference to anything directly on the page, just to the abstracted names. This will help with readability and insulate page layout changes.
  • then: … is just making sure we made it to the right place. We could add any other assertions here.

Next we add a test for an invalid user. This tests the layout when an error is displayed.

class SignupPageIT extends GebReportingSpec {

    def "Signup Test for Invalid User"() {

        given: "I'm at the sign up form"
        to SignupPage

        when: "I signup as an invalid user"
        emailField = "geb@test.com"
        firstNameField = "dave"
        lastNameField = "lastname"
        submitButton.click(SignupPage)

        then: "I'm at the sign up page again "
        at SignupPage
        errorHeading == "Error!"
    }
}
  • when: … We fill out the form with our invalid user “dave”. Even though we already set the destination for the submit click, it is overridden here since we expect an error and should be redirected to the signup page.
  • then: … is where the assertions live. For the failed test we assert we are at the correct page and the errorHeading was set to the expected text.

Run the Geb Tests

The tests can be run from your IDE since the test runner is built on JUnit or via maven. Normally I like SureFire for Maven to run the units tests and it can definitely be used here for integration tests but takes some setup. The conflict comes with the execution scope. Normally SureFire will kick off with mvn test and for that we only want unit tests. You can always exclude the integration tests but then you need to define a 2nd SureFire just for Integration tests. An alternative is the FailSafe maven plugin. FailSafe is designed for integration tests and will automatically find any tests ending with *IT and run them. To setup just add the following to the pom in the build section.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.6</version>
    <executions>
        <execution>
            <id>integration-test</id>
            <phase>integration-test</phase>
            <goals>
                <goal>integration-test</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Now you can run the test from the command line with mvn integration-test

Testing Series
This post is one in a series about end to end testing of Spring MVC applications. If you are interested in other levels of application testing then check out the series index here.
Tags:,

7 Comments

  1. Alex December 8, 2016
  2. Sheena September 19, 2016
    • Ryan September 19, 2016
  3. Jose April 3, 2016
  4. James November 8, 2013
  5. Ronald November 6, 2013
    • Ryan November 6, 2013

Leave a Reply

Your email address will not be published. Required fields are marked *