Selenium integration with maven
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.
In your browser, go to the application homepage: http://localhost:9080/integration-testing-post and you should see the following:
Once you’ve navigated around the application, stop it by pressing ctrl-C in the console.
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.
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
CarIntegrationTest is a Selenium Integration Test. It extends SeleniumTestCase. The source code is below:
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:
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.
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:
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
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
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(); } }
"*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 content of this test method was generated using the Selenium IDE plugin for 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:- Start up the web application running in Jetty
- Start up the Selenium Server
<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>
- 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