Sunday, March 6, 2016

Integrate Cucumber with Geb: Productive BDD Automation with Style

In this post, I show how Cucumber can be integrated with Geb for implementing end-to-end tests under the BDD paradigm, in a productive and stylish manner. As a system under testing, I choose the BBC website and test basic navigation on BBC news pages. The code is available on github.

Background

Geb is a Groovy framework for automation. It offers a powerful DSL on top of  WebDriver (aka Selenium 2). The benefit from using Geb instead of WebDriver directly is that Geb takes responsibility of the technicalities, removes the boilerplate and allows the developer to focus on the test framework he builds. It does that by exploiting Groovy meta-programming capabilities and Groovy's inherent conciseness. It has also built in support for test automation code design patterns, like the Page Object and the Loadable Component patterns, that have been proven critical for minimizing the code maintenance cost of end-to-end internal business test frameworks.

Cucumber is the most famous test framework to support the BDD paradigm. What BDD is and how much benefit agile teams can take by following it, has been discussed in one of my previous posts, about integrating cucumber with WebDriver. In this post, I follow the same objective, i.e. to automate a scenario where the user lands on the BBC home page, navigates to a news category (e.g. Business) and finally shares a video on facebook. In this post I effectively replace WebDriver with Geb and show the value of this replacement. The reader is highly recommended to read that previous post before continuing with this one, as well as comparing the two github repositories (bbc-webdriver-cucumber and bbc-geb-cucumber).

Setup

As a build and dependency management tool, I use Gradle. The build.gradle file follows. 

The Geb dependencies are geb-core and geb-junit4, as in this demo I use Junit4 (alternatives include TestNG and Spock). The Cucumber dependencies are cucumber-groovy and cucumber-junit. Also, in this demo I use the selenium-firefox-driver. Finally, the standard groovy-all dependency is added to be able to compile and run Groovy and junit to be able to use the Junit4 runner via the Cucumber runner class. Notice that some dependencies appear to be compile time dependencies, while some others testCompile. This is due to my habit when I develop test frameworks to store Cucumber Step Definition and Page Object classes under the src/ while the test runner class under the test/ directory. If someone selects to put them all under the test/ directory, then the testCompile status is enough for all the dependencies.

BBC News Feature

The demo scenario is similar to the one presented in the bbc-webdriver-cucumber demo, but is simplified and written in user terms.

Geb Page Objects

Due to its importance in writing effective and maintainable test frameworks, the Page Object pattern has built in support on Geb. In a sense, Geb forces the developer to follow this pattern, while WebDriver leaves it to his decision/knowledge. Let's look at NewsPage class to see how a Geb Page class looks like.


As one can observe the NewsPage class extends the geb.Page class and defines three static variables, url, at and content. The first one is of type String and defines the url of the web page modeled by the NewsPage class. The second one, i.e. at, is of type Closure and defines a boolean post-condition of the web page loading, i.e. a condition whose true value makes us sure the web page has been loaded correctly. This is normally a check to see if some representative web element is displayed. The content variable is of type Closure, which in turn contains a set of closures, each one representing a web element. For instance, the news closure represents the "NEWS" text in the upper left corner of the BBC news page that is written with white font on top of a red background. The news closure contains a locator for that web element. Geb uses a very powerful JQuery-style locator syntax. Although Geb allows you to work with the org.openqa.selenium.By static methods for defining locators, the gebbish JQuery-style syntax is much more flexible. When called, this closure returns a proxy of the locator. Similarly to WebDriver PageFactory functionality, Geb's locators are lazy, in the sense that they are resolved against the active web page the first time a method is called on them. For instance, when someone calls the at closure, the following steps take place:
  1. news closure is called 
  2. news closure returns a proxy of the web element located by the specified locator.
  3. the method isDisplayed() is called on that proxy
  4. the web element located by the specified locator is initialized (NoSuchElementException can be thrown if no element is found) 
  5. isDisplayed() returns true or false, depending on if the web element is currently displayed or not on the active web page
It might not be obvious to someone unfamiliar with Groovy, but news.displayed is equivalent to news().isDisplayed(). If a closure has no argument you can omit the parentheses. Also, the Java-style named getters getX() for a field x (or isX() if x is of boolean type) are equivalent with x in Groovy.

The second, i.e. categoryLink, closure defined inside the content closure is even more interesting. Since categoryLink is a closure, it can take parameters. This gives you the very powerful ability to define a group of locators, that differ only in the specified parameters. For instance, categoryLink can locate all the links in the BBC news page navigation bar. Each one of these links points to a different news category page, e.g. Business, Politics etc. All those links differ only on their text (and of course on the url they point to) and therefore can be defined in a unified manner.

Finally, NewsPage class defines a method for navigating to the NewsCategory page representing the specified category. This method returns the NewsCategorPage object following the "strong" page object pattern, that requires for any method to return the page object that will be active after its call. The call categoryLink(category) returns the proxy of the link located by the locator corresponding to the specified category and then the click() method is called on this element to navigate to the specified category web page. At this point we need to notify Geb that the active page object has been changed. This is done with the browser.page(new NewsCategoryPage(category: category)) call. A lot of things take place here, so let's examine this call in detail.

First a new NewsCategoryPage object is created and its category field is initialized with the specified category string. Then, this object is passed as argument to browser.page method. The browser object is a field of the superclass geb.Page. Its type is the Browser class, which is effectively a wrapper around a WebDriver object. By default Geb uses the FirefoxDriver, but this is configurable. The browser object exposes the driver object, e.g. you can access it by writing browser.driver (which is equivalent with browser.getDriver()) and work with this directly but in most cases you don't need to do this. You may notice that there is no return statement in the selectCategory method. This is a famous Groovy idiom, the method returns the object that the last expression is evaluated to. In this case, browser.page(new NewsCategoryPage(category: category)) returns the page object that is passed as argument, which is of NewsCategoryPage type.

Steps Implementation

The Step Definition file follows.

As one can easily observe, there is no class, step definitions are written in a script form. In Groovy, classes are optional, meaning that Groovy can be used as a scripting language. Before and After are cucumber Hooks that run before and after each scenario since I don't pass any parameter (they could run only before/after some scenarios specified by their parameters). 

The Before hook block deserves some explanation. With the BindingUpdater class, we inject a Browser object in this script. BindingUpdater is a Geb class which wraps around the Groovy Binding class. This is normally used in Groovy scripts to inject objects. Any programming language supports some concept of Binding (some of them call it "environment"), e.g. if you define a variable x = 5, then the language at runtime creates a variable and bounds it to the value "5". After running the Before hook, each scenario has access to the injected browser object, which has been bounded to an instance of Browser class. 

For instance, the first step implementation block is to HomePage. In Groovy, parentheses are optional, if a method has a single parameter, e.g. to HomePage is equivalent with to(HomePage). Second, in Groovy classes are first class citizens, e.g. to refer to HomePage class, you can write HomePage, instead of HomePage.class (the ".class" is optional). Since there is no method named to taking as argument the HomePage class, one could ask how this script runs successfuly. The trick is that such a to method is defined in the browser object that has been injected to the script. Therefore, to HomePage is equivalent with browser.to(HomePage.class). You could write it like that, but the Gebbish point is to get rid of the boilerplate and stick to the concise and stylish to HomePage syntax. The idea is you should focus on your test goal which at this point can be described as "go to BBC home web page". All the rest are technicalities that Geb handles for you.

If you read this script, you will realize I only used the to method to load the first web page, which is the BBC home page. For all the rest navigations, I followed the "strong" page object pattern with calling methods exposed by page objects to navigate to other web pages, like page.selectLatestNews() which navigates from the BBC home page to the BBC News page. Each time the page object (this is equivalent to browser.page) is the page object corresponding to the active web page. This is the result of me following the "strong" page object pattern. The page object is defined with type geb.Page in the Browser class, which does not define any selectLatestNews method, but Groovy supports a dynamic type system (aka duck typing) by default (you can switch to static typing by using annotations though), i.e. it resolves the object types at run time. As a developer, I know that at this point the page object is of type HomePage and therefore has a method selectLatestNews. In the following line, I check if the active web page is the news category page to assure that this web page has been loaded correctly. This will invoke the at closure defined in the NewsPage class. 

Notice the difference with the following step implementation that includes def newsCategoryPage = page.selectCategory(category). This call implements the navigation from the news page to the news category page for the category specified in the feature file. Since this step can navigate to different category pages an at call with parameter the NewsCategoryPage class is not enough. NewsCategoryPage class models all the news category pages, while I want to be sure that the active web page is the one corresponding to the category specified in the feature file. To this end, I need to assign the page object returned by selectCategory and pass this as the at method parameter. Browser class overloads the at method, so that you can either pass a Page class (or list of potential Page classes) or a Page class instance (or a list of potential Page class instances).

Furthermore, notice that the first step implementation don't need to make an at call, since the to method does an at call at the end to check if the target web page has been loaded correctly.

Finally, all step definition "methods" are actually not methods but calls to Cucumber methods that take as argument a string pattern and a closure. If the final argument of a method is a closure, then Groovy allows this to be written outside of the parentheses. For instance the first step, i.e. 
Given(~/^the user is landed at the home page$/) { -> to HomePage }
is equivalent to 
Given(~/^the user is landed at the home page$/, { -> to HomePage }).

Runner Class

The Groovy version of the Cucumber Runner class is very similar to the Java version:

Run this on eclipse with "Run As"->"JUnit Test" or by command line with gradle clean test.

Outro

To sum up, Geb boosts the productivity of the automation developer beyond any preceding level. To achieve this it allows you to focus on what really matters, the web elements of pages under test and the interaction between web pages as defined by your product requirements. Gebs manages all the technical details for you. Also Geb forces developers to follow design patterns like the Page Object and the Loadable Component patterns which are critical for the minimization of the code maintenance cost. Since it is heavily relied on the Groovy meta-programming features, it requires the developer has some level of maturity with Groovy. The JQuery locator syntax might look unfamiliar to back-end developers but is really easy to learn and Geb has excellent documentation (not only about the locators). It integrates seamlessly with Cucumber and together they make a perfect combination for developing end-to-end test frameworks that require web page automation and are developed by agile teams following the BDD paradigm.

Note 04/02/2017:

I have upgraded to Selenium 3. 
You will have to download the geckodriver, unzip it and set the path to it on src/test/resources/GebConfig.groovy.
The project was tested with: 
  • selenium version 3.0.1
  • geckodriver 0.14.0
  • firefox 51.0.1