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
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.
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)
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:
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
Sample Spring MVC + Test Suite (10063 downloads)
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.
Hi,
do you have any video tutorials ?
I cloned this project into intelliJ and have a few questions as I am trying to learn Geb: https://github.com/geb/geb-example-gradle
Why can I only run from command line and not from GebConfig.groovy? I would like to debug to understand how the project flows because I have no idea how we get from the base url to the GebishOrgTest.groovy file and am getting errors when trying to run project from the IDE.
How do I click on OK button on a browser alert window in Geb? Since I cannot look up the page element name using firebug, is there a direct code that can just identify this OK button and click on it?
Geb has some documented features here alert-and-confirm-dialogs. I have not directly used it but worth updating this tutorial to try it.
Some questions
1: Why can’t i create the gebConfig file on said package?
2: where should be created the objects/tests classes?
Thanks for this post. This tutorial is probably the coolest thing since ice cream. I also appreciate that you included the source code and other relevant code examples.
Just out of curiosity, have you ever devised a method for abstracting data using the Geb framework, that is based on the environment? For example, instead of using a JQuery selector in a page object, use a variable that could have environment dependent data inserted in the page model object from a properties file or something? If so, extra points will be rewarded for an example of that! Thanks again, and i will take my answer off the air.
Nice overview, however the sources are not correct. The IndexControllerTest class can not be compiled because it contains a extra dot (model()..attributeExists(“page_error”)) but after fixing this the mvn integration-test can still not run. It is looking for a dependency that can not be resolved.
Thanks for letting me know. I posted a v2 that has a few fixes and mvn integration-test completed successfully on Mac 10.8 & 10.9.
Fixes:- The typo you listed above.
- Fixed the application context for the mock service test.
- Removed the references in the pom to a local jacoco.exec file. This is for a later post and not needed for Geb.