Thomas Sundberg

April 17, 2009

Integration test a web application with Selenium

Filed under: J2EE, Java, Selenium — Tags: , , , , , , , , , , , — Thomas Sundberg @ 11:59

We want to build a web application and we want to test it automatically.

One solution is to make sure that whenever we perform an integration test, the application is deployed on a servlet container, a Selenium server is started and the application is verified through a web browser. That is automating the deployment process and the testing process. No person should start a browser, fill out a form and verify that the result is the expected.

Set up a maven project

A simple web application

Deploy on a servlet container automatically

An integration test

Start Selenium from Maven

Build and run the integration test

Set up a maven project

Lets start with this Maven project structure:

    src -------+- main ----+- java
                   |               |
                   |               +- webapp
                   |
                   +- test ----+- java
    pom.xml

The pom should contain this to start with

<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.agical.experimental</groupId>
    <version>1.0-SNAPSHOT</version>
    <artifactId>SeleniumDemo</artifactId>
    <name>Selenium Demo</name>
    <packaging>war</packaging>
    <description>Demonstration on how to test a simple web app using Maven and Selenium</description>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-idea-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <downloadJavadocs>true</downloadJavadocs>
                    <downloadSources>true</downloadSources>
                    <jdkLevel>1.5</jdkLevel>
                    <jdkName>1.5</jdkName>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>2.5.6</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.5</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

The things to note is that we want to use annotations and therefore we require to use at least Java 1.5, as stated in the maven-compiler-plugin. Next we want to create Intellij IDEA project files and if we state that we want both source and javadoc in the maven-idea-plugin, they will be downloaded as well. We will be able to read the javadoc as well as the source code for the third party libs from IDEA.

I plan to use Spring to simplify the controller so I add a requirement to the spring-webmvc and finally I want to be able to write test using JUnit so I specify that we want a dependency to junit.

Create the IDEA project files with

mvn idea:idea

and start IDEA so we can create the rest of the application.

A simple web application

Lets start with a simple test that should verify our controller in

srctestjavacomagicalexperimentalcontrollerRegistrationTest.java

package com.agical.experimental.controller;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;

import java.util.Map;

public class RegistrationTest {

    @Test
    public void register() {
        Registration registration = new Registration();
        String expectedName = "Thomas";
        String expectedSurname = "Sundberg";
        Model model = new ExtendedModelMap();
        String expectedLandingPage = "registrationConfirmation";

        String actualLandingPage = registration.registerGet(expectedName, expectedSurname, model);

        assertThat(actualLandingPage, is(expectedLandingPage));

        assertTrue(model.containsAttribute("name"));
        assertTrue(model.containsAttribute("surname"));

        Map modelMap = model.asMap();

        String actualName = (String) modelMap.get("name");
        assertThat(actualName, is(expectedName));

        String actualSurname = (String) modelMap.get("surname");
        assertThat(actualSurname, is(expectedSurname));
    }

}

Nothing really exciting here. We define a small test that will verify that some values will be available in a model and that we refer to a landing page after the controller has been called.

This will of course not even compile since the production code isn’t available. Lets create a simple controller in the production code and run the test until it works properly. Create
srcmainjavacomagicalexperimentalcontrollerRegistration.java

package com.agical.experimental.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/registration")
public class Registration {

    @RequestMapping(method = RequestMethod.GET)
    public String registerGet(String name, String surname, Model model) {
        model.addAttribute("name", name);
        model.addAttribute("surname", surname);

        return "registrationConfirmation";
    }
}

A simple controller. This should be enough so we can compile and run a unit test. Lets do that.

mvn test

Ok, it compiled and the unit test passed. Let’s package it as a web application now. We need to start with web.xml, create it as
srcmainwebappWEB-INFweb.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
        "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>Selenium Demo</display-name>

    <servlet>
        <servlet-name>Controller</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Controller</servlet-name>
        <url-pattern>/Controller/*</url-pattern>
    </servlet-mapping>

</web-app>

Spring expect us to define a file named using this pattern: <servlet name in web.xml>-servlet.xml so lets define
srcmainwebappWEB-INFController-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="

http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans-2.5.xsd


http://www.springframework.org/schema/context

        http://www.springframework.org/schema/context/spring-context-2.5.xsd"
       default-autowire="byName">

    <context:component-scan base-package="com.agical.experimental"/>

    <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

We are missing a landing page and we are missing a start page. The start page isn’t mandatory, but it is a nice way to test stuff either manually or automatically. Lets start with a landing page in
srcmainwebappWEB-INFjspregistrationConfirmation.jsp

<%--@elvariable id="name" type="String"--%>
<%--@elvariable id="surname" type="String"--%>
<%@ page contentType="text/html;charset=ISO-8859-1" language="java" %>

Welcome ${name} ${surname}!

The start page could be defined as below in
srcmainwebappindex.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>

<form action="./Controller/registration" method="GET">
    Name: <input id="name" type="text" name="name"/>
    <br>
    Surname: <input id="surname" type="text" name="surname"/>
    <br>
    <input id="submit" type="submit" value="Submit"/>
</form>

</body>
</html>

We should now be able to build the web application and deploy it.

Lets build it using

mvn clean install

Deploy it somewhere, a running JBoss for example and try to access it from this url:

http://localhost:8080/SeleniumDemo-1.0-SNAPSHOT/

Deploy on a servlet container automatically

We were able to build a web application and deploy it manually. This is of course necessary if we ever should be able to automate the process. One tool that could help us with automatic deployment from Maven is Cargo. Lets add the stuff needed to deploy the application using Cargo to the pom. Cargo can deploy stuff in a lot of different applications servers but since this demonstration isn’t really about deploying in different environments, we will use an embedded servlet container. Cargo has Jetty embedded so the only thing we need to add to deploy the application to Jetty is this snippet from the pom:

            <plugin>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven2-plugin</artifactId>

                <executions>
                    <execution>
                        <id>start-container</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>start</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>stop-container</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>stop</goal>
                        </goals>
                    </execution>
                </executions>

                <configuration>
                    <container>
                        <containerId>jetty6x</containerId>
                        <type>embedded</type>
                    </container>
                    <wait>false</wait>
                </configuration>
            </plugin>

The important stuff here are:

  • the executions section where I define that Jetty need to be started before the integration test phase in
    maven and that it should not be terminated until the integration phase is done.
  • the wait section must be set to false, the script will pause if it isn’t and the follwing steps will not be executed.

We can’t just add this section and hope it will work, we need to a repository where we can download the plugin definition.

    <pluginRepositories>
        <pluginRepository>
            <id>mojo-snapshots</id>
            <name>codehause mojo snapshots</name>
            <layout>default</layout>
            <url>http://repository.codehaus.org</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

A working pom should look something like this now:

<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.agical.experimental</groupId>
    <version>1.0-SNAPSHOT</version>
    <artifactId>SeleniumDemo</artifactId>
    <name>Selenium Demo</name>
    <packaging>war</packaging>
    <description>Demonstration on how to test a simple web app using Maven and Selenium</description>

    <pluginRepositories>
        <pluginRepository>
            <id>mojo-snapshots</id>
            <name>codehause mojo snapshots</name>
            <layout>default</layout>
            <url>http://repository.codehaus.org</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-idea-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <downloadJavadocs>true</downloadJavadocs>
                    <downloadSources>true</downloadSources>
                    <jdkLevel>1.5</jdkLevel>
                    <jdkName>1.5</jdkName>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven2-plugin</artifactId>

                <executions>
                    <execution>
                        <id>start-container</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>start</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>stop-container</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>stop</goal>
                        </goals>
                    </execution>
                </executions>

                <configuration>
                    <container>
                        <containerId>jetty6x</containerId>
                        <type>embedded</type>
                    </container>
                    <wait>false</wait>
                </configuration>
            </plugin>

        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>2.5.6</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.5</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>

Perform

mvn integration-test

and note that a Jetty is started in port 8080. If you remove the <wait>false</wait> above, you should be able to access the web application. It will not work because we are still missing a small piece that is needed for Jetty.

An integration test

With a web application that we can deploy from Maven, all we need is a good way to test it automatically.

First we need to add one more dependency in the pom:

        <dependency>
            <groupId>org.seleniumhq.selenium.client-drivers</groupId>
            <artifactId>selenium-java-client-driver</artifactId>
            <version>1.0-beta-2</version>
        </dependency>

Re-create the IDEA project

mvn idea:idea

And finally add a integration test in
srctestjavaitcomagicalexperimentalcontrollerSeleniumTest.java

package it.com.agical.experimental.controller;

import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;
import static junit.framework.Assert.assertTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class SeleniumTest {

    private static final String DEFAULT_WAIT_PERIOD = "3000";
    private Selenium selenium;

    @Before
    public void setUp() throws Exception {
        String host = "http://localhost:8080/";
        String browser = "*firefox";
        // String browser = "*iexplore";

        // configure the selenium client
        selenium = new DefaultSelenium("localhost", 4444, browser, host);

        // launch the browser window
        selenium.start();
    }

    @After
    public void tearDown() throws Exception {
        selenium.stop();
    }

    @Test
    public void verifySimpleFormAndRespone() {
        selenium.open("SeleniumDemo-1.0-SNAPSHOT/");
        selenium.waitForPageToLoad(DEFAULT_WAIT_PERIOD);

        String name = "Patricia";
        String surname = "Persson";

        selenium.type("name", name);
        selenium.type("surname", surname);

        selenium.click("submit");
        selenium.waitForPageToLoad(DEFAULT_WAIT_PERIOD);

        assertTrue(selenium.isTextPresent(name));
        assertTrue(selenium.isTextPresent(surname));
    }
}

There are two things to note here:

  • We expect that there will exist a way to create a DefaultSelenium that should run on port 4444
  • We have defined the class in the package ‘it.com.agical.experimental.controller’

I will add some things to the pom so that we will be able to call an instance of Selenium on port 4444 later.

The other thing, the package ‘it’, is defined so we can differ the normal unit tests and the integration tests. There are basically two major ways to differ integration tests from unit tests.

  • Define all integration tests in different modules
  • Define all integration tests in a special package and configure the Surefire plug in to include or exclude
    the different tests depending on the goal that maven was called with.

I will use the latter approach in this example. The Surefire plug in can be configured like this:

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>**/it/**/*.java</exclude>
                    </excludes>
                </configuration>
                <executions>
                    <execution>
                        <id>integration-test</id>
                        <goals>
                            <goal>test</goal>
                        </goals>
                        <phase>integration-test</phase>
                        <configuration>
                            <excludes>
                                <exclude>none</exclude>
                            </excludes>
                            <includes>
                                <include>**/it/**/*.java</include>
                            </includes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

Start Selenium from Maven

Lets add stuff to the pom so we can run a Selenium remote control.

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>selenium-maven-plugin</artifactId>
                <version>1.0-rc-1</version>
                <executions>
                    <execution>
                        <id>start-selenium</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>start-server</goal>
                        </goals>
                        <configuration>
                            <background>true</background>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

This will start a Selenium server in the background and it will in its turn be able to drive a browser from the integration test defined above.

Build and run the integration test

We should be done by now, lets try.

mvn integration-test

It fails!

We are missing something. It turns out that we need to add a dependency to jstl in the pom. Add the dependency below:

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

A working pom should now look like this:

<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.agical.experimental</groupId>
    <version>1.0-SNAPSHOT</version>
    <artifactId>SeleniumDemo</artifactId>
    <name>Selenium Demo</name>
    <packaging>war</packaging>
    <description>Demonstration on how to test a simple web app using Maven and Selenium</description>

    <repositories>
        <repository>
            <id>nexus</id>
            <name>Nexus Repository</name>
            <url>http://nexus.openqa.org/content/repositories/releases/</url>
            <layout>default</layout>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>

    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>mojo-snapshots</id>
            <name>codehause mojo snapshots</name>
            <layout>default</layout>
            <url>http://repository.codehaus.org</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-idea-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <downloadJavadocs>true</downloadJavadocs>
                    <downloadSources>true</downloadSources>
                    <jdkLevel>1.5</jdkLevel>
                    <jdkName>1.5</jdkName>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.codehaus.cargo</groupId>
                <artifactId>cargo-maven2-plugin</artifactId>

                <executions>
                    <execution>
                        <id>start-container</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>start</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>stop-container</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>stop</goal>
                        </goals>
                    </execution>
                </executions>

                <configuration>
                    <container>
                        <containerId>jetty6x</containerId>
                        <type>embedded</type>
                    </container>
                    <wait>false</wait>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>selenium-maven-plugin</artifactId>
                <version>1.0-rc-1</version>
                <executions>
                    <execution>
                        <id>start-selenium</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>start-server</goal>
                        </goals>
                        <configuration>
                            <background>true</background>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>**/it/**/*.java</exclude>
                    </excludes>
                </configuration>
                <executions>
                    <execution>
                        <id>integration-test</id>
                        <goals>
                            <goal>test</goal>
                        </goals>
                        <phase>integration-test</phase>
                        <configuration>
                            <excludes>
                                <exclude>none</exclude>
                            </excludes>
                            <includes>
                                <include>**/it/**/*.java</include>
                            </includes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>2.5.6</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.5</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.seleniumhq.selenium.client-drivers</groupId>
            <artifactId>selenium-java-client-driver</artifactId>
            <version>1.0-beta-2</version>
            <!--<scope>test</scope>-->
        </dependency>

    </dependencies>

</project>

Run

mvn integration-test

again and the result should be that the application is built and tested from a browser.

1 Comment »

  1. […] Integration test a web application with┬áSelenium […]

    Pingback by Selenium « Maedashogun's Blog — October 29, 2010 @ 20:03


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Silver is the New Black Theme. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 61 other followers

%d bloggers like this: