In this topic I'll describ how to create acceptance tests using Cucumber and Selenium. The first part will be about maven, because we don't want to run our acceptance tests with unit tests. The second one is about a web driver UI module. The last one, I'll show how to create a test using Cucumber and Selenium.
Would you like to see your acceptance tests running togheter your unit tests? I don't think so. Do you also need to provision your environments before accetance starts, right? Let's see how to do it using the integration-tests phase of maven lifecycle. What we need is to configure a new build profile for integration-tests phase:
<!-- Acceptance Tests profile -->
<profile>
<id>acceptance-tests</id>
<activation>
<property>
<name>acceptance-test</name>
<value>run</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.12</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
How about we keep ours sources of acceptance tests separately? Cool, this makes code organization easier. Below is how to configure the sources in src/acceptance/java and resources in src/acceptance/resources:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/acceptance/java</source>
</sources>
</configuration>
</execution>
<execution>
<id>add-resource</id>
<phase>generate-sources</phase>
<goals>
<goal>add-test-resource</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/acceptance/resources</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
Now we need to configure selenium server. This plugin defines two executions: start-seleniun-server which executes before acceptace tests, and stop-selenium-server which executes after acceptance tests, for more information see build lifecycle.
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>selenium-maven-plugin</artifactId>
<executions>
<execution>
<id>start-selenium-server</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start-server</goal>
</goals>
<configuration>
<background>true</background>
</configuration>
</execution>
<execution>
<id>stop-selenium-server</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop-server</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
Now we have our new phase configured.
What I call WebDriverUI is the module that understands your UI. Here is where you will develop all your code that interacts with you UI using selenium. Lets call this module:
<groupId>acceptance</groupId>
<artifactId>acceptance-webdriverui</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Web Driver UI for Acceptance Test</name>
<description> Web Driver Utility for Acceptance Test</description>
Below the dependencies you will need:
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>2.31.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-server</artifactId>
<version>2.31.0</version>
<scope>compile</scope>
</dependency>
Why create a separated module for UI interaction? If you do this, changes in your UI will impact only in this module, also, if this module has a good implementation, the components created here will be reused several times on your acceptance module. Below in example of an UI component that reflect a login page:
package acceptance.test.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
/**
* login page example.
*
*/
public class LoginPage {
private WebDriver driver;
@FindBy(id = "username")
private WebElement username;
@FindBy(id = "password")
private WebElement password;
@FindBy(id = "performLogin")
private WebElement btnLogin;
public LoginPage(WebDriver driver) {
this.driver = driver;
initElements();
}
/**
* Lazy initialize of page elements. Useful for @FindBy annotations.
*/
public void initElements() {
PageFactory.initElements(driver, this);
}
/**
* Perform login.
*/
public void login(String userName, String password) {
this.username.sendKeys(userName);
this.password.sendKeys(password);
btnLogin.click();
}
/**
* Load the login page.
*/
public void goToPage() {
driver.get("localhost:8080/home");
initElements();
}
/**
* Verify if is on login page.
*/
public boolean isOnPage() {
return username.isDisplayed();
}
}
Cucumber is a tool for Behaviour Drive Development where text is written in a business-readable domain-specific language and serves as documentation, automated tests and development-aid - all rolled into one format.
For our test you may wish to create a module with you acceptance tests, the dependencies you will need are:
<dependency>
<groupId>acceptance</groupId>
<artifactId>>acceptance-webdriverui</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.1.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.1.3</version>
<scope>compile</scope>
</dependency>
Alright, lets create our acceptance test: First we need to create a class that its only job is to delegate the tests to cucumber:
package acceptance.test.login;
import org.junit.runner.RunWith;
import cucumber.api.junit.Cucumber;
/**
* No code needed, just the @RunWith(Cucumber.class), so cucumber will run
* looking for all DBB tests (*.feature files) found in this package.
*
*/
@RunWith(Cucumber.class)
public class LoginTest {
}
Now we need to create our step definition, this file describes our feature, so we create the file: LoginTest.feature. This file will be placed on: src/acceptance/resources/acceptance/test/login
Feature: It should be possible to perform login and logout on home page.
Scenario: Perform login
Given Access login page
When I try to login with user acceptance and password acceptance
Then I should be in my home page
Now we can create a class that will peform the test described by LoginTest.feature:
package acceptance.test.login;
import org.junit.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import acceptance.test.pages.LoginPage;
import cucumber.api.java.After;
import cucumber.api.java.Before;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
/**
* Script class.
*/
public class LoginStepDefinitions {
private WebDriver driver;
private LoginPage loginPage;
@Before
public void setUp() throws WebDriverException {
driver = new FirefoxDriver();
// Initialize data, if needed
// Initialize pages
loginPage = new LoginPage(driver);
}
@After
public void tearDown() {
if (driver != null) {
driver.close();
}
}
@Given("^Access login page")
public void given() {
loginPage.goToPage();
Assert.assertTrue(loginPage.isOnPage());
}
@When("^I try to login with user (\\w+) and password (.*)$")
public void performLong(String user, String password) {
loginPage.login(user, password);
}
@Then("^I should be in my home page")
public void assertLogin() {
// Search for some element in your home page,
// or create a home page element, and assert you are there
WebElement element = driver.findElement(By.id("searchInputText"));
Assert.assertNotNull(element);
Assert.assertTrue(element.isDisplayed());
}
}
Now we can run our acceptance tests only executing: mvn integration-test -P acceptance-tests
A good practice is to use your API to create data for tests, so you could create another module specific for accessing your API. Through this module you will also be testing your application API.
Next post will be about using Selenium Grid :)