siark.com blog

October 1, 2010

Behaviour Driven Development With JBehave Web 3, Selenium and Maven 2 on OS X Snow Leopard

For integration testing on the siark.com webapp I’m using JBehave 3 and Selenium. As an initial test all I’m going to do is test that the home page displays the message “Welcome to the siark.com website.” when a user navigates to /igallery, the root of the siark.com website.I’m using the Page Object pattern to construct the tests.

1 Write a scenario and save it in a file called home.story in src/test/resources/com/siark/igallery/stories

Scenario: User opens home page

Given the user opens the home page
Then the home page should be displayed

2 Create a generic page class that I call IGalleryPage. The java file is created in src/test/java/com/siark/igallery.

package com.siark.igallery.pages;

import org.jbehave.web.selenium.SeleniumPage;

import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.condition.ConditionRunner;

public class IGalleryPage extends SeleniumPage {
	
	public IGalleryPage(Selenium selenium, ConditionRunner conditionRunner) {
		super(selenium, conditionRunner);
	}
}

3 Create a HomePage class that deals with the home page specifically. The java file is created in src/test/java/com/siark/igallery/pages.

package com.siark.igallery.pages;

import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.condition.ConditionRunner;

public class HomePage extends IGalleryPage {
	
	public HomePage(Selenium selenium, ConditionRunner conditionRunner) {
		super(selenium, conditionRunner);
	}
	
	public void open() {
		open("/igallery/");
	}
	
	public void verifyPage() {
		textIsVisible("Welcome to the siark.com website.");
	}
}

4 Now create a class called IGallerySteps that defines the annotations used in the story. The java file is created in src/test/java/com/siark/igallery.

package com.siark.igallery;

import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Then;

import com.siark.igallery.pages.FindSteps;
import com.siark.igallery.pages.HomePage;
import com.siark.igallery.pages.PageFactory;

public class IGallerySteps {
	private final PageFactory pageFactory;
	private HomePage home;
	
	public IGallerySteps(PageFactory pageFactory) {
		this.pageFactory = pageFactory;
	}
	
	@Given("the user opens the home page")
	public void theUserOpensTheHomePage() {        
		home = pageFactory.home();
		home.open();        
	}
	
	@Then("the home page should be displayed")
	public void theHomePageShouldBeDisplayed(){
		home.verifyPage();
	}
}

5 A factory class called PageFactory is used to get page classes. The java class is created in src/test/java/com/siark/igallery/pages.

package com.siark.igallery.pages;

import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.condition.ConditionRunner;

public class PageFactory {
	private final Selenium selenium;
	private final ConditionRunner conditionRunner;
	
	public PageFactory(Selenium selenium, ConditionRunner conditionRunner) {
		this.selenium = selenium;
		this.conditionRunner = conditionRunner;
	}
	
	public HomePage home() {
		return new HomePage(selenium, conditionRunner);
	}
}

6 To configure and run the tests, an embedable runnable class is created, which I have called IGalleryStories. The java file is created in src/test/java/com/siark/igallery.

package com.siark.igallery;

import static java.util.Arrays.asList;
import static org.jbehave.core.io.CodeLocations.codeLocationFromClass;
import static org.jbehave.core.reporters.StoryReporterBuilder.Format.CONSOLE;
import static org.jbehave.core.reporters.StoryReporterBuilder.Format.HTML;
import static org.jbehave.core.reporters.StoryReporterBuilder.Format.TXT;
import static org.jbehave.core.reporters.StoryReporterBuilder.Format.XML;

import java.util.List;

import org.jbehave.core.Embeddable;
import org.jbehave.core.configuration.Configuration;
import org.jbehave.core.io.CodeLocations;
import org.jbehave.core.io.LoadFromClasspath;
import org.jbehave.core.io.StoryFinder;
import org.jbehave.core.junit.JUnitStories;
import org.jbehave.core.reporters.ConsoleOutput;
import org.jbehave.core.reporters.StoryReporter;
import org.jbehave.core.reporters.StoryReporterBuilder;
import org.jbehave.core.steps.CandidateSteps;
import org.jbehave.core.steps.InstanceStepsFactory;
import org.jbehave.core.steps.SilentStepMonitor;
import org.jbehave.web.selenium.SeleniumConfiguration;
import org.jbehave.web.selenium.SeleniumContext;
import org.jbehave.web.selenium.SeleniumStepMonitor;

import com.siark.igallery.pages.PageFactory;
import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.condition.ConditionRunner;

public class IGalleryStories extends JUnitStories {
	
	private Selenium selenium = SeleniumConfiguration.defaultSelenium();
	private ConditionRunner conditionRunner = SeleniumConfiguration.defaultConditionRunner(selenium);
	private PageFactory pageFactory = new PageFactory(selenium, conditionRunner);
	private SeleniumContext seleniumContext = new SeleniumContext();

	@Override
	public Configuration configuration() {
		Class<? extends Embeddable> embeddableClass = this.getClass();
		return new SeleniumConfiguration()
			.useSelenium(selenium)
			.useSeleniumContext(seleniumContext)
			.useStepMonitor(new SeleniumStepMonitor(selenium, seleniumContext, new SilentStepMonitor()))
			.useStoryLoader(new LoadFromClasspath(embeddableClass))
			.useStoryReporterBuilder(new StoryReporterBuilder() {
			
				@Override
				public StoryReporter reporterFor(String storyPath, Format format) {
					if (format == CONSOLE) {
						return new ConsoleOutput() {
							@Override
							public void beforeScenario(String title) {
								seleniumContext.setCurrentScenario(title);
								super.beforeScenario(title);
							}
						};
					} else {
						return super.reporterFor(storyPath, format);
					}
				}
			}
			.withCodeLocation(CodeLocations.codeLocationFromClass(embeddableClass))
			.withDefaultFormats()
			.withFormats(CONSOLE, TXT, HTML, XML));
	}
	
	@Override
	public List<CandidateSteps> candidateSteps() {
		return new InstanceStepsFactory(configuration(), new IGallerySteps(pageFactory), new FailingScenarioScreenshotCapture(selenium))
		.createCandidateSteps();
	}
	
	@Override
	protected List<String> storyPaths() {
		return new StoryFinder().findPaths(codeLocationFromClass(this.getClass()).getFile(), asList("**/*.story"), null);
	}
}

This class is actually the same class provided on the JBehave website 🙂

7 The last class is also from the JBehave website and is the FailingScenarioScreenshotCapture used in IGalleryStories. It is also created in src/test/java/com/siark/igallery.

package com.siark.igallery;

import org.jbehave.core.annotations.AfterScenario;
import org.jbehave.core.annotations.AfterScenario.Outcome;
import org.jbehave.web.selenium.PerScenarioSeleniumSteps;

import com.thoughtworks.selenium.Selenium;

public class FailingScenarioScreenshotCapture extends PerScenarioSeleniumSteps {
	public FailingScenarioScreenshotCapture(Selenium selenium) {
		super(selenium);
	}
	
	@AfterScenario(uponOutcome = Outcome.FAILURE)
	public void afterFailingScenario() throws Exception {
		String home = System.getenv("HOME");
		selenium.captureScreenshot(home+"/failedScenario.png");
	}
}

8 Finally I need to add a dependency for jbehave-web-selenium, the maven-jetty-plugin plugin so that the Jetty application server can be started and stopped and the igallery.war file can be deployed to it, the selenium-maven-plugin plugin to start and stop the Selenium server and lastly the jbehave-maven-plugin plugin to run the integration tests. The Jetty server will listen on port 8080 for HTTP requests, and the Selenium server will listen on port 4444.

<project xmlns="http://maven.apache.org/POM/4.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.siark.igallery</groupId>
	<artifactId>igallery</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>Siark iGallery Webapp</name>
	<url>http://www.siark.com</url>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>org.jbehave.web</groupId>
			<artifactId>jbehave-web-selenium</artifactId>
			<version>3.0</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>igallery</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>maven-jetty-plugin</artifactId>
				<configuration>
					<webApp>${project.build.directory}/igallery.war</webApp>
					<webAppConfig>
						<contextPath>/igallery</contextPath>
					</webAppConfig>
				</configuration>
				<executions>
					<execution>
						<id>start-jetty</id>
						<phase>pre-integration-test</phase>
						<goals>
							<goal>run-war</goal>
						</goals>
						<configuration>
							<scanIntervalSeconds>0</scanIntervalSeconds>
							<daemon>true</daemon>
						</configuration>
					</execution>
					<execution>
						<id>stop-jetty</id>
						<phase>post-integration-test</phase>
						<goals>
							<goal>stop</goal>
						</goals>
						<configuration>
							<stopKey>stopJetty</stopKey>
							<stopPort>9966</stopPort>
						</configuration>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>selenium-maven-plugin</artifactId>
				<version>1.0.1</version>
				<executions>
					<execution>
						<id>start-selenium</id>
						<phase>pre-integration-test</phase>
						<goals>
							<goal>start-server</goal>
						</goals>
						<configuration>
							<background>true</background>
							<debug>false</debug>
							<logOutput>true</logOutput>
						</configuration>
					</execution>
					<execution>
						<id>stop-selenium</id>
						<phase>post-integration-test</phase>
						<goals>
							<goal>stop-server</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>org.jbehave</groupId>
				<artifactId>jbehave-maven-plugin</artifactId>
				<version>3.0</version>
				<executions>
					<execution>
						<id>run-stories</id>
						<phase>integration-test</phase>
						<configuration>
							<includes>
								<include>**/*Stories.java</include>
							</includes>
							<scope>test</scope>
						</configuration>
						<goals>
							<goal>run-stories-as-embeddables</goal>
						</goals>
					</execution>
				</executions>
				<dependencies>
					<dependency>
						<groupId>org.jbehave.web</groupId>
						<artifactId>jbehave-web-selenium</artifactId>
						<version>3.0</version>
					</dependency>
				</dependencies>
			</plugin>
		</plugins>
	</build>
	<properties>
		<org.springframework.version>3.0.4.RELEASE</org.springframework.version>
	</properties>
</project>

Create a free website or blog at WordPress.com.