Continuous Testing with Selenium and JBehave using Page Objects
Join the DZone community and get the full member experience.
Join For FreeSince Mike‘s inception we have always sought to automate as much of our testing as possible. For some time now we have been using Selenium for our functional/acceptance tests, and thus far have been very happy with this approach. Initially, we decided to use Selenese-style tests, as we felt this would enable non-programmers to help maintain and extend the coverage of our tests. Someone with a basic grasp of markup and enough ability to make small re-factorings to the HTML code generated by Selenium IDE. However, as the range of features provided by our platform has started to grow we have found that we are doing a lot of ctrl+c and ctrl+v of the Selenese scripts, and generally violating our DRY principles. After some internal debate, we finally settled on a new approach that adopts Behaviour Driven Development (BDD) techniques. This works nicely with our Agile, User Story based approach to development and (as you might expect) our advanced internal practices when using Continuous Integration for our own purposes.
BDD Framework Selection
The Mike codebase is predominately Java, so it seemed sensible to choose a Java-based BDD framework. We could have opted for something like the Ruby rockstars fave Cucumber, which is a now well-established, but in the end decided upon JBehave. It got our vote for a number of reasons:
- It’s an active project, with regular production releases.
- It’s ‘maven-ized’ and you can bind a handy JBehave plugin to appropriate life-cycle phases
- It provides a web testing sub-component that gives you a nice, simple abstraction for Selenium
- Scenarios can be defined in plain-text, just like Cucumber
- It integrates into an IDE just like any other xUnit-based testing framework (right Click > Run As > JUnit Test)
The plain-text scenarios were of particular interest, as they allow
non-programmers to continue to author test flows for each story. On the
downside, it does mean that only developers can provide the
implementation of these scenarios. But overall, it provides a good fit
for our team profile.
An Example
I’ll walk through an example of a simple JBehave BDD-style scenario, that seeks to test that perennial fave – the Java Petstore web application:
Scenario: Successful Login
Given the user opens the home page
When the user clicks the enter store link
Then the store front page should be displayed
When the user clicks the sign in link
Then the store login page should be displayed
When the user enters username j2ee
And the user enters password j2ee
And the user clicks the login button
Then the store front page should be displayed for user ABC
This combination of ‘Given’, ‘When’ and ‘Then’ maps very nicely to a test context, event and expected outcome for each of the various pathways through a User Story.
So now that we have our scenario, stored in a text file name ‘login_scenarios’, using JBehave we need to create two additional classes. These are:
- a trivial subclass of org.jbehave.scenario.Scenario whose name maps to the associated text file (LoginScenarios.java)
- a subclass of org.jbehave.web.selenium.SeleniumSteps (LoginSteps.java) that provides an implementation for each of the ‘Given’, ‘When’ and ‘Then’ statements.
For example:
@Given("the user opens the home page")
public void theUserOpensTheHomePage(){
homePage = new HomePage(selenium, runner);
homePage.open("/jpetstore");
}
Notice how JBehave uses simple annotations to map the scenario elements to Java methods. You’ll probably also notice the use of a ‘page’ object in the method body, which performs the actual heavy-lifting of the tests. In addition, the methods in the JBehave-provided base class SeleniumSteps can be overridden as required. For example, override createSelenium() if you need to provide a custom instance of Selenium with an alternative configuration.
Page Objects
Within a web application UI there are areas that our tests interact with. Using the Page Object pattern allows us to intuitively model these pages as objects within the test code. This massively reduces the amount of duplicated code and means that if the UI changes, the fixes need only be applied in one place. This means we get our DRY testing mojo back after our experiences with copy ‘n’ pasted Selenese markup. To make things even simpler, its a good idea to create an abstract base (Page.java) class that exposes a series of useful methods to its concrete children. Here’s an example of a class that represents the login page of our demo Java petstore app.
public class StoreLoginPage extends Page {
public StoreLoginPage(Selenium selenium, ConditionRunner runner) {
super(selenium, runner);
}
@Override
public void verifyPage() {
textIsVisible("Please enter your username and password.");
}
public void verifyPresenceOfInvalidCredentialsErrorMessage(){
textIsVisible("Invalid username or password. Signon failed.");
}
public void typeUsername(String username){
type("//input[@name='username']", username);
}
public void typePassword(String password){
type("//input[@name='password']", password);
}
public StoreFrontPage clickLoginButton(){
clickButton("//input[@value='Login']");
waitForPageToLoad();
return new StoreFrontPage(selenium, runner);
}
}
Once these page classes are wired into our SeleniumSteps subclasses, we can use the action (open, click, type etc) or verification style methods to drive the tests.
Maven Magic
As mentioned earlier, we selected JBehave in part because of its Maven integration. There is plug-in you can configure to execute the tests during the required phase:
<project>
[...]
<plugins>
[...]
<plugin>
<groupId>org.jbehave</groupId>
<artifactId>jbehave-maven-plugin</artifactId>
<version>2.5.1</version>
<executions>
<execution>
<id>run-scenarios-found</id>
<phase>integration-test</phase>
<configuration>
<scenarioClassNames>
<scenarioClassName>
com.mikeci.jpetstore.test.scenario.LoginScenarios
</scenarioClassName>
</scenarioClassNames>
<classLoaderInjected>false</classLoaderInjected>
<skip>false</skip>
<ignoreFailure>false</ignoreFailure>
<batch>true</batch>
<scope>test</scope>
</configuration>
<goals>
<goal>run-scenarios</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.jbehave.web</groupId>
<artifactId>jbehave-web-selenium</artifactId>
<version>2.1.4</version>
</dependency>
</dependencies>
</plugin>
[...]
</plugins>
[...]
</project>
Obviously, there are some additional services that we need when these scenarios are executed – the app must be deployed and available along with a running selenium server. In the petstore example, the Maven cargo and selenium plugins are used for these purposes. Have a look at the full unexpurgated pom.xml to see how they are configured.
Running The Example
Prerequisites are a Subversion client, Maven 2.2.x, Java 1.6.x and Firefox installed. Localhost ports 8080 and 4444 need to be unoccupied too.
Check out the project:
~$ svn co https://mikesamples.googlecode.com/svn/trunk/maven/jpetstore-maven-multi-module/ jpetstore
~$ cd jpetstore
Run the Maven build:
~$ mvn clean install -Pmikeci,run-functional-tests,run-on-desktop
Et voila, this should build the JPetstore web app, deploy it to Jetty, start Selenium and run two JBehave scenarios against it. You could use this example to bootstrap your own JBehave/Selenium implementation and get started with BDD in no time at all.
Published at DZone with permission of Adam Leggett, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
13 Impressive Ways To Improve the Developer’s Experience by Using AI
-
How Web3 Is Driving Social and Financial Empowerment
-
MLOps: Definition, Importance, and Implementation
-
Low Code vs. Traditional Development: A Comprehensive Comparison
Comments