Selenium integration with maven

Posted by Mufi on 7:18 AM with No comments
We use Maven on the vast majority of our projects and we often use Jetty as a web container in plain Java web projects (i.e. those that don’t require a full JEE container). Often the deployment environment is not Jetty (it might be Oracle AS or WebLogic for example) but Jetty is so well integrated with Maven that the costs of not developing on the deployment platform are outweighed by the ease-of-development benefits.
Selenium is a common choice for writing integration tests for web application – and one that we have used on various projects in the past. The idea is that you interact with your web application using Firefox and the Selenium IDE plugin records the test. You can then replay this test as part of the ‘integration-test’ Maven phase for your application, hence building up a suite of integration tests to run.
Putting all of this together, we’d like to share with you the Maven configurations and Java code that we use to accomplish automated one-step Selenium integration testing for a web application running in Jetty using Maven.
For the purposes of this post, we will refer to a Spring-MVC Java web application that keeps track of cars. It has the ability to add/delete/edit/list cars. To keep things simple, it does not use a database but keeps a list of cars in memory. The full code for the application can be downloaded from here.
Pre-requisites: Maven installed, some understanding of Selenium is helpful.

Run the web application

Download the source code (available here) and unzip it to a folder on your filesystem. Open a console and navigate to the folder with the code in it and execute the following:
mvn jetty:run
In your browser, go to the application homepage: http://localhost:9080/integration-testing-post and you should see the following:
Integration Testing Cars Application homepage
Integration Testing Cars Application homepage
Once you’ve navigated around the application, stop it by pressing ctrl-C in the console.

Open the application in Eclipse

Navigate to the directory to which you downloaded the source code in the console. Run the following to Generate the Eclipse project descriptors:
mvn eclipse:eclipse
Then, open Eclipse and run File > Import and choose Existing Projects Into Workspace. Hit ‘next’ and then browse for the directory into which you downloaded the code. Click ok and then Eclipse should find your new project. Double-click on it to import it. You may have to add the M2_REPO classpath variable to your workspace if you’ve not already done so. See here for more info.

Create the Selenium Integration Test

The integration tests are in the /src/test/java/com/tsl/example/cars/integration/ folder of the project. SeleniumIntegrationTest is the base class for all Selenium tests. It starts up a Selenium client in the setup() method and stops it in the tearDown() method.
package com.tsl.example.cars.integration;
 
import junit.framework.TestCase;
 
import org.openqa.selenium.server.SeleniumServer;
 
import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;
 
public abstract class SeleniumTestCase extends TestCase {
 
 protected Selenium selenium;
 
 @Override
 protected void setUp() throws Exception {
  super.setUp();
        selenium = new DefaultSelenium(
          "localhost", 
          SeleniumServer.getDefaultPort(), 
          "*iehta", 
          "http://localhost:9080");
        selenium.start();
 }
 
    @Override
    protected void tearDown() throws Exception {
     selenium.stop();
     super.tearDown();
    }
}
For more information on the parameters passed in the constructor of DefaultSelenium, see the Selenium client documentation. The most important is that which specifies the browser that Selenium should use to run the tests. In this case, it is set to "*iehta" which means Internet Explorer. To change to Firefox, use "*firefox".
CarIntegrationTest is a Selenium Integration Test. It extends SeleniumTestCase. The source code is below:
package com.tsl.example.cars.integration;
 
public class CarIntegrationTest extends SeleniumTestCase {
 
 public void testAddCar() throws Exception {
  selenium.open("/integration-testing-post/cars/list.html");
  selenium.click("link=Add Car");
  selenium.waitForPageToLoad("30000");
  selenium.type("make", "Mercedes");
  selenium.type("model", "SLK");
  selenium.click("btnSave");
  selenium.waitForPageToLoad("30000");
  assertTrue(selenium.isTextPresent("Mercedes"));
  assertTrue(selenium.isTextPresent("SLK"));
 } 
}
The testAddCar() method tests that we can add a car (a Mercedes SLK) using the web application. It assumes that you start at the start page of the application, click on the ‘add car’ link, enter the make and model and click save. It waits up to 30secs for the application to save the car and tests that on the resulting page (which is the list of cars), the words ‘Mercedes’ and ‘SLK’ appear. This allows us to assume that the car was correctly added.
The content of this test method was generated using the Selenium IDE plugin for Firefox:
Selenium IDE plugin in Firefox
Selenium IDE plugin in Firefox
I’m not going to go into detail here on how to use the Selenium IDE but basically it acts like a Macro recorder – recording your steps as you manually perform the integration test in your browser and then allowing you to play it back. I prefer standard Java syntax for the test instead of the ‘Selenese’ default. You can export Java code from a new test case by choosing File > Export Test Case As > Java – Selenium RC. I tend to create a new class manually in Eclipse for the test and then copy over just the testXXX methods from the Java source code that I exported from Selenium IDE.

Running the integration tests in Jetty using Maven

Once we have created an integration test we want to run it using Maven. There are a few things we want to happen before the integration test runs though:
  1. Start up the web application running in Jetty
  2. Start up the Selenium Server
We accomplish this using the following section in the Maven POM file (in the root folder of the downloaded code):
   <build>
      <plugins>
         <plugin>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>maven-jetty-plugin</artifactId>
            <configuration>
               <scanIntervalSeconds>5</scanIntervalSeconds>
               <stopPort>9966</stopPort>
               <stopKey>foo</stopKey>
               <connectors>
                  <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
                     <port>9080</port>
                     <maxIdleTime>60000</maxIdleTime>
                  </connector>
               </connectors>
            </configuration>
            <executions>
               <execution>
                  <id>start-jetty</id>
                  <phase>pre-integration-test</phase>
                  <goals>
                     <goal>run</goal>
                  </goals>
                  <configuration>
                     <daemon>true</daemon>
                  </configuration>
               </execution>
               <execution>
                  <id>stop-jetty</id>
                  <phase>post-integration-test</phase>
                  <goals>
                     <goal>stop</goal>
                  </goals>
               </execution>
            </executions>
         </plugin>
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
               <source>1.5</source>
               <target>1.5</target>
               <encoding>UTF-8</encoding>
            </configuration>
         </plugin>
         <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>selenium-maven-plugin</artifactId>
            <version>1.0-beta-3</version>
            <executions>
               <execution>
                  <id>start</id>
                  <phase>pre-integration-test</phase>
                  <goals>
                     <goal>start-server</goal>
                  </goals>
                  <configuration>
                     <background>true</background>
                     <logOutput>true</logOutput>
                     <multiWindow>true</multiWindow>
                  </configuration>
               </execution>
               <execution>
                  <id>stop</id>
                  <phase>post-integration-test</phase>
                  <goals>
                     <goal>stop-server</goal>
                  </goals>
               </execution>
            </executions>
         </plugin>
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
               <excludes>
                  <exclude>**/integration/*Test.java
                  </exclude>
               </excludes>
            </configuration>
            <executions>
               <execution>
                  <id>integration-tests</id>
                  <phase>integration-test</phase>
                  <goals>
                     <goal>test</goal>
                  </goals>
                  <configuration>
                     <skip>false</skip>
                     <excludes>
                        <exclude>none</exclude>
                     </excludes>
                     <includes>
                        <include>**/integration/*Test.java
                        </include>
                     </includes>
                  </configuration>
               </execution>
            </executions>
         </plugin>
      </plugins>
   </build>
I’m among the first to acknowledge the benefits of Maven, but at times the XML syntax required to configure it can be a bit scary! Here’s the basic explanation of all this:
  • The Jetty plugin is configured to run Jetty on port 9080 – I prefer to avoid potential conflicts with other applications running on 8080 which is quite commonly used.
  • In the pre-integration-test Maven phase (executed before the integration tests are run), the run goal of the Maven Jetty plugin is executed. The configuration element allows us to specify that that this is a daemon process
  • In the post-integration-test phase, we run the stop goal of the Maven Jetty plugin to shut down Jetty.
  • In the pre-integration-test phase we run the start-server goal of the Maven Selenium Plugin
  • In the post-integration-test phase, we run the stop-server goal of the Maven Selenium plugin to shut down the Selenium server
  • The Maven Surefire plugin is configured to not execute the integration tests as part of the normal test phase but instead in the integration-test phase.

Executing the Integration tests

Although the Maven configuration looks verbose and complicated, it gives great results. Just executing the single command below in the console causes the integration tests to run without interference:
mvn integration-test

What about Cargo?

When I was developing this code, I noticed that many people seemed to use the Maven Cargo plugin to start up the relevant application server. I had very mixed results using this with different application servers and it didn’t seem to work at all with Jetty – lots of Classpath problems if I remember rightly. In the end, the plain old Maven Jetty plugin did the job just fine.