Skip Navigation

Integration Testing with Maven

Chapter 8

8.1 Introduction

This chapter covers integration testing with Maven.

8.2. Running a Selenium Test

8.2.1. Task

You need to write a Selenium test to test a web application or a web site.

8.2.2. Action

Selenium is a very straightforward way to test a web application, you can write a unit test in any number of languages and then use the Selenium Remote Control to automate a browser and perform assertions about the presence of text or UI elements in a page. This recipe uses a TestNG test which includes the appropriate Selenium code to test a well known web site: http://www.twitter.com. In a Maven project devoted to integration testing, the following test class connects to a Twitter user page and verifies the correct user name.

Example 8.1. TwitterTest a Selenium Test written in TestNG

package org.sonatype.mcookbook;

import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.SeleniumException;

public class TwitterTest {

private Selenium selenium;

@BeforeClass
public void startSelenium() {
this.selenium = new DefaultSelenium("localhost", 4444, "*safari", "http://www.twitter.com");
this.selenium.start();
}

@Test
@Parameters( { "user", "name" })
public void testTwitter(String user, String name) {
try {
selenium.open("http://www.twitter.com/" + user);
selenium.waitForPageToLoad("3000");
assert selenium.isTextPresent(name);

} catch (SeleniumException e) {
throw e;
}
}

@AfterClass(alwaysRun = true)
public void stopSelenium() {
this.selenium.stop();
}

}

The following POM contains the following configuration to configure the Selenium server and the Selenium integration tests:

  • The Maven Surefire plugin is configured to skip tests. This will prevent the Surefire plugin from executing any unit tests during the test phase.
  • An execution of the Surefire plugin is configured not to skip tests during the integration-test phase. This configuration coupled with the previous configuration simply moves the execution of the tests into the integration-test phase of the lifecycle.
  • The user and name properties that the testTwitter() method relies upon are configured via the systemProperties configuration parameter of the Maven Surefire plugin.
  • The Selenium Maven plugin's start-server goal is configured to run in the pre-integration-test phase. This has the effect of starting Selenium before the Surefire plugin executes the integration tests. No configuration options are passed to the start-server goal so Selenium will be started on a default port of 4444.
  • A dependency on selenium-java-client-driver exposes the Selenium API to the test cases and allows the Surefire test to interact with the Selenium server.

Example 8.2. POM configuring SureFire to Execute Selenium Remote Control

<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>org.sonatype.mcookbook</groupId>
<artifactId>selenium-reddit</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>selenium-reddit</name>
<build>
<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.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
<systemProperties>
<property>
<name>user</name>
<value>tobrien</value>
</property>
<property>
<name>name</name>
<value>Tim O'Brien</value>
</property>
</systemProperties>
</configuration>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<skip>false</skip>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>selenium-maven-plugin</artifactId>
<executions>
<execution>
<phase>pre-integration-test</phase>
<goals>
<goal>start-server</goal>
</goals>
<configuration>
<background>true</background>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>5.10</version>
<classifier>jdk15</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium.client-drivers</groupId>
<artifactId>selenium-java-client-driver</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

If you run Maven to the integration-test phase, Maven will start the Selenium server before it executes the integration-test phase. In the output shown below, the section that starts with selenium:start-server is the pre-integration-test execution of the start-server goal in the Selenium Maven plugin. This is followed by an invocation of the test goal from the Maven Surefire plugin which will execute the TestNG tests which rely on the Selenium server.

$ mvn integration-test
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building selenium-reddit
[INFO] task-segment: [integration-test]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources {execution: default-resources}]
[WARNING] Using platform encoding (MacRoman actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO] [compiler:compile {execution: default-compile}]
[INFO] Nothing to compile - all classes are up to date
[INFO] [resources:testResources {execution: default-testResources}]
[WARNING] Using platform encoding (MacRoman actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO] [compiler:testCompile {execution: default-testCompile}]
[INFO] Compiling 1 source file to /Users/Tim/Library/Code/sonatype/ maven-cookbook/mcookbook-examples/integrate/selenium-reddit/ target/test-classes
[INFO] [surefire:test {execution: default-test}]
[INFO] Tests are skipped.
[INFO] [jar:jar {execution: default-jar}]
[INFO] Building jar: /Users/Tim/Library/Code/sonatype/maven-cookbook/ mcookbook-examples/integrate/selenium-reddit/ target/selenium-reddit-1.0-SNAPSHOT.jar
[INFO] [selenium:start-server {execution: default}]
Created dir: /Users/Tim/Library/Code/sonatype/maven-cookbook/ mcookbook-examples/integrate/selenium-reddit/target/selenium
Launching Selenium Server
Waiting for Selenium Server...
[INFO] User extensions: /Users/Tim/Library/Code/sonatype/maven-cookbook/ mcookbook-examples/integrate/selenium-reddit/ target/selenium/user-extensions.js
23:41:46,726 INFO [SeleniumServer] Java: Apple Inc. 14.1-b02-90
23:41:46,727 INFO [SeleniumServer] OS: Mac OS X 10.6.1 x86_64
23:41:46,735 INFO [SeleniumServer] v1.0.1 [2697], with Core
23:41:46,829 INFO [HttpServer] Version Jetty/5.1.x
23:41:46,830 INFO [Container] Started HttpContext[/selenium-server/driver, /selenium-server/driver]
23:41:46,832 INFO [Container] Started HttpContext[/selenium-server, /selenium-server]
23:41:46,832 INFO [Container] Started HttpContext[/,/]
23:41:46,846 INFO [SocketListener] Started SocketListener on 0.0.0.0:4444
23:41:46,846 INFO [Container] Started org.mortbay.jetty.Server@4a4e79f1
23:41:47,262 INFO [Credential] Checking Resource aliases
Selenium Server started
[INFO] [surefire:test {execution: default}]
[INFO] Surefire report directory: /Users/Tim/Library/Code/sonatype/ maven-cookbook/mcookbook-examples/integrate/selenium-reddit/ target/surefire-reports

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running TestSuite
23:41:48,178 INFO [org.mortbay.util.Credential] Checking Resource aliases
23:41:48,184 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Command request: getNewBrowserSession[*safari, http://www.twitter.com, ] on session null
23:41:48,187 INFO [org.openqa.selenium.server.BrowserSessionFactory]
creating new remote session
23:41:48,436 INFO [org.openqa.selenium.server.BrowserSessionFactory]
Allocated session 2a66870a3c894a60a19ef6f2e7a1dc74 for http://www.twitter.com, launching...
23:41:48,519 INFO [org.openqa.selenium.server.browserlaunchers.
SafariCustomProfileLauncher] Launching Safari to visit...
23:41:53,251 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Got result: OK, on session
23:41:53,265 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Command request: open[http://www.twitter.com/tobrien, ] on session
23:41:56,231 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Got result: OK on session
23:41:56,256 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Command request: waitForPageToLoad[3000, ] on session
23:41:56,270 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Got result: OK on session
23:41:56,284 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Command request: isTextPresent[Tim O'Brien, ] on session
23:41:56,343 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Got result: OK,true on session
23:41:56,352 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Command request: testComplete[, ] on session
23:41:56,370 INFO [org.openqa.selenium.server.SeleniumDriverResourceHandler]
Got result: OK on session
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 8.609 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 18 seconds
[INFO] Finished at: Fri Nov 27 23:41:56 CST 2009
[INFO] Final Memory: 32M/80M
[INFO] ------------------------------------------------------------------------

8.3. Running Integration Tests Against a Servlet Container

8.3.1. Task

You are testing a web application and you need to start a servlet container prior to running integration tests.

8.3.2. Action

Use the Maven Jetty Plugin to start an instance of a server prior to running your integration tests. Assume that you are writing integration tests to test the sample web application project that was introduced in Section 6.1, “Running a Web Application in a Servlet Container”. If you have some Selenium tests for the web application, you can start an instance of Jetty running the web application as a daemon in the pre-integration-test phase and stop the instance in the post-integration-test phase.

Your web application is in the org.sonatype.mcookbook:sample-web project and your integration tests are in the org.sonatype-mcookbook:sample-web-it project. You can configure the Maven Dependency plugin to copy the WAR to the sample-web-it project during the package phase, then during the pre-integation-test phase you will configure the Maven Jetty plugin to start an instance of Jetty to run the sample-web.war as well as to start the Selenium server. The Maven Surefire plugin will then execute the unit tests, and after the tests are complete the Jetty server will be stopped in the post-integration-test phase.

Our TestNG integration tests is as follows. It is going to connect to the default Jetty host and port of localhost:8080, and submit a form that calculates the tenth number in the Fibonacci sequence (F10). This integration test is stored in ${basedir}/src/test/java.

package org.sonatype.mcookbook;

import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.SeleniumException;

public class FibTest {

private Selenium selenium;

@BeforeClass
public void startSelenium() {
this.selenium = new DefaultSelenium("localhost", 4444, "*safari",
"http://localhost:8080");
this.selenium.start();
}

@Test public void testSequence() throws Exception {
selenium.open("/sample-web/");
selenium.type("index", "10");
selenium.click("//input[@value='Calculate']");
selenium.waitForPageToLoad("30000");
assert selenium.isTextPresent("55");
}

@AfterClass(alwaysRun = true)
public void stopSelenium() {
this.selenium.stop();
}

}

The POM to configure the sample-web-it is shown below.

<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>org.sonatype.mcookbook</groupId>
<artifactId>sample-web-it</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>sample-web-it</name>
<build>
<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.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>1
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.sonatype.mcookbook</groupId>
<artifactId>sample-web</artifactId>
<version>1.0-SNAPSHOT</version>
<type>war</type>
<overWrite>true</overWrite>
<destFileName>sample-web.war</destFileName>
</artifactItem>
</artifactItems>
<outputDirectory>
${project.build.directory}/war
</outputDirectory>
<overWriteReleases>true</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
<executions>
<execution>
<phase>integration-test</phase>2
<goals>
<goal>test</goal>
</goals>
<configuration>
<skip>false</skip>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>selenium-maven-plugin</artifactId>
<executions>
<execution>
<phase>pre-integration-test</phase>3
<goals>
<goal>start-server<</goal>
</goals>
<configuration>
<background>true</background>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.22</version>
<executions>
<execution>
<id>start-jetty</id>4
<phase>pre-integration-test</phase>
<goals>
<goal>run-war</goal>
</goals>
<configuration>
<contextPath>sample-web</contextPath>
<daemon>true</daemon>
<webApp>
${project.build.directory}/war/sample-web.war
</webApp>
</configuration>
</execution>
<execution>
<id>stop-jetty</id>5
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
<configuration>6
<stopPort>9991</stopPort>
<stopKey>test</stopKey>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>5.10</version>
<classifier>jdk15</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium.client-drivers</groupId>
<artifactId>selenium-java-client-driver</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.sonatype.mcookbook</groupId>7
<artifactId>sample-web</artifactId>
<version>1.0-SNAPSHOT</version>
<type>war</type>
</dependency>
</dependencies>
</project>

This POM contains the following configuration:

1

The Maven Dependency plugin is configured to copy the sample-web web application archive to the ${project.build.directory}/war/sample-web.war file. The copy goal is bound to the package phase.

2

The Surefire plugin is configured to skip all tests during the test phase and to execute all tests during the integration-test phase.

3

The Selenium server is started during the pre-integration-test phase as a background process. This makes the Selenium server available to any integration tests that need to interact with a browser.

4

The start-jetty execution starts the Jetty server in the pre-integration-test phase. This execution references the WAR downloaded by the Maven Dependency plugin and it also sets the context path to "sample-web". Setting daemon to "true" runs Jetty in the background and continues to progress through the Maven lifecycle. If daemon were set to "false", the Maven build would stop and wait for the Jetty process to complete.

5

The stop-jetty execution stops the Jetty server after the integration tests have been executed.

6

To stop the Jetty server we need to supply a stop port and a stop key. Both the run-war and stop goals of the Jetty plugin will use this configuration so it is defined at the plugin level instead of the execution level. The stop port defines the port number that Jetty will listen to for a stop command, and the stop key is a string that will be used by the jetty:stop goal to stop the Jetty process.

7

Adding a dependency to the web application project that is being tested will make sure that this project is ordered after the web application in a multi-module build.

 

When you run the integration-test phase of this build, you will see that Maven runs through the lifecycle and starts Selenium and Jetty before running the integration tests.

$ mvn clean install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building sample-web-it
[INFO] task-segment: [clean, integration-test]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean {execution: default-clean}]
...
[INFO] [resources:resources {execution: default-resources}]
...
[INFO] [resources:testResources {execution: default-testResources}]
...
[INFO] [compiler:testCompile {execution: default-testCompile}]
[INFO] Compiling 1 source file to ~/Code/sonatype/maven-cookbook/mcookbook-examples/integrate/sample-web-it/ target/test-classes
[INFO] [surefire:test {execution: default-test}]
[INFO] Tests are skipped.
[INFO] [jar:jar {execution: default-jar}]
...
[INFO] [dependency:copy {execution: copy}]
...
[INFO] [selenium:start-server {execution: default}]
Created dir: ~/maven-cookbook/mcookbook-examples/integrate/sample-web-it/ target/selenium
Launching Selenium Server
Waiting for Selenium Server...
...
[INFO] [dependency:copy {execution: copy}]
[INFO] Configured Artifact: org.sonatype.mcookbook:sample-web:1.0-SNAPSHOT:war
[INFO] Copying sample-web-1.0-SNAPSHOT.war to ~/maven-cookbook/mcookbook-examples/integrate/sample-web-it/ target/war/sample-web.war
[INFO] [jetty:run-war {execution: start-jetty}]
[INFO] Configuring Jetty for project: sample-web-it 2009-11-29 04:21:37.994:INFO::Logging to STDERR via org.mortbay.log.StdErrLog
[INFO] Context path = /sample-web
[INFO] Starting jetty 6.1.22 ... 2009-11-29 04:21:38.683:INFO::Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
[INFO] [surefire:test {execution: default}]

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running TestSuite
04:21:39,436 INFO [org.mortbay.util.Credential] Checking Resource aliases
...
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.597 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] [jetty:stop {execution: stop-jetty}]
[INFO] Stopping server 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 13 seconds
[INFO] Finished at: Sun Nov 29 04:21:42 CST 2009
[INFO] Final Memory: 48M/99M
[INFO] ------------------------------------------------------------------------
2009-11-29 04:21:43.201:INFO::Shutdown hook executing
2009-11-29 04:21:43.822:INFO::Shutdown hook complete