Geb, Selenium, Cucumber & Maven Tutorial
A while ago I wrote some tutorials on writing Selenium tests in Java. I’ve been using Selenium on and off for a while and I’ve found somethings are always tricky:
- The Java code tends to be very verbose and has a lot of boiler plate. But you’re not tied to the backend language, so you could use any language, or even a DSL.
- JUnit is great for unit testing, but it’s not really ideal for functional tests. These tests could be Product Owner facing, and framework choice should suite this.
- I’ve found that the main drivers (e.g the Firefox driver) can be slow, prone to crashing, or not cleaning up after themselves.
It struck me that I needed to research an alternative, so this led me to the following stack:
- HtmlUnit driver rather than FirefoxDriver. While it’s not a featured as the other drivers, it’s faster and more reliable.
- Geb for defining pages, navigating and assertions.
- Cucumber Groovy for creating test scenarios. It’s really well known and understood, integration with major IDE and build tools is mature.
I’ve put the code for this tutorial on Github.
Maven Set-up
You can create a basic Maven web-app very easily:
mvn archetype:generate -DgroupId=test -DartifactId=test-webapp -Dversion=1.0.0-SNAPSHOT -DarchetypeArtifactId=maven-archetype-webapp
We want it to run integration tests, so add the Cargo and Failsafe plugins:
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<version>1.2.0</version>
<executions>
<execution>
<id>start</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
You can now execute mvm cargo:run to run a local server for testing, or mvm install to run tests automatically.
Groovy Set-up
As Geb is Groovy based, and some of our code will be in Groovy, we’ll need the correct, matching, compiler and dependencies. There are a few options to do this, I’ve found this combination to be most reliable (note I’m using v1.8):
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generateTestStubs</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<providerSelection>1.8</providerSelection>
</configuration>
</execution>
</executions>
</plugin>
...
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>1.8.6</version>
<scope>test</scope>
</dependency>
Geb Set-up
We’ll need some Geb/Selenium dependencies:
<dependency>
<groupId>org.gebish</groupId>
<artifactId>geb-core</artifactId>
<version>0.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-htmlunit-driver</artifactId>
<version>2.36.0</version>
<scope>test</scope>
</dependency>
Geb requires some basic config stating which driver to use and when to navigate relative to, in the form of a GebConfig.groovy file is the class path root:
driver = "htmlunit"
baseUrl = "http://localhost:8080/test-webapp/"
Cucumber Set-up
Again, we’ll need some dependencies:
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-groovy</artifactId>
<version>1.1.5</version>
<scope>test</scope>
</dependency>
We’ll need a single test so that Maven runs it during the integration-test phase:
@RunWith(Cucumber.class)
class RunCukesIT {
}
Putting it Together
To bind Geb and Cucumber we need an env.groovy that lives with the step defs:
package stepdefs
import geb.Browser
import geb.binding.BindingUpdater
import static cucumber.api.groovy.Hooks.After
import static cucumber.api.groovy.Hooks.Before
Before() {
bindingUpdater = new BindingUpdater(binding, new Browser())
bindingUpdater.initialize()
}
After() {
bindingUpdater.remove()
}
Phew! That’s the boiler plate over and done with! Fortunately, you only need to do it once, and you’re not going to see too much Groovy code from now on. Instead, much of the code will be DSL-ish.
The first thing we’ll need is a couple of page objects. I’m including one for the index and one for a create order page, e.g.:
package pages
import geb.Page
class CreateOrderPage extends Page {
static url = "create-order.jsp"
static at = { $("h2").text() == "Create Order" }
static content = {
form { $("form") }
item { form.find("input[name='item']") }
amount { form.find("input[name='amount']") }
submit { form.find("input[type='submit']") }
}
def createOrder(item1, amount1) {
item = item1
amount = amount1
submit.click()
}
}
This page object class provides a few things:
urlis the path to the page, relative to thebaseUrlin the config.atis a closure that verifies you’re on the page.contentcreates a series of fields on the page object mapped to page elements, in this case a form.createOrderis a method that fills the form and submits it.
The step defs provides an insight in how we use the pages:
package stepdefs
import pages.CreateOrderPage
import pages.IndexPage
import static cucumber.api.groovy.EN.*
Given(~"I'm at the index page") { ->
to IndexPage
at IndexPage
}
When(~"I click 'Create Order'") { ->
page.clickCreateOrder()
}
Then(~"I see the create order page") { ->
at CreateOrderPage
}
Given(~"I'm at the create order page") { ->
to CreateOrderPage
at CreateOrderPage
}
When(~"I enter (.*) (.*)") { item, amount ->
page.createOrder(item, amount)
}
This file uses at and to from Geb to navigate to the appropriate pages, and the implicit page object to access the page You can see two examples here, page.clickCreateOrder() refers to the index page, whereas page.createOrder(..) refers to the order page.
Finally, you can create create-order.feature:
Feature: Create Order
Scenario: Navigate to Create Order
Given I'm at the index page
When I click 'Create Order'
Then I see the create order page
Scenario: Input Order
Given I'm at the create order page
When I enter test 1.0
Then I'm at the create order page
You can now run the RunCukesIT or the create-order.feature within your IDE (after starting Cargo), or as part of a build using mvn install.
Exercise for the Reader
Check out the code, add a new order confirmation page and create a test scenario for it.
Conclusion
Combining Geb with Cucumber provides a compact, expressive, and PO/QA friendly way to create browser tests. Using the HTML Unit driver provides speed and reliability, but at the expense of power. Using Groovy instead of Java provides a terser way to write tests, which reduces boiler, plate and takes advantage of Groovy’s closures.
Geb is currently v0.9.2. I’ve not had problems with the libraries, and you’ll find that hunting down tutorials (such as this one) can be very helpful. Some of the examples are incomplete, or out dated and hopefully this tutorial will help