Skip to content

Instantly share code, notes, and snippets.

@gmcoringa
Last active December 22, 2015 08:59
Show Gist options
  • Save gmcoringa/6449255 to your computer and use it in GitHub Desktop.
Save gmcoringa/6449255 to your computer and use it in GitHub Desktop.
BDD with Cucumber and Selenium

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.

Maven integration lifecycle

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.

WebDriverUI

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

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

Tips

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

Next post will be about using Selenium Grid :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment