Integration test a web application with Selenium

Filed under: Java, Selenium, Test automation, — Tags: Cargo, Integration test, JUnit, Jetty, Maven, Spring, TDD, Web application — Thomas Sundberg — 2009-04-17

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

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

src/test/java/com/agical/experimental/controller/RegistrationTest.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

src/main/java/com/agical/experimental/controller/Registration.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

src/main/webapp/WEB-INF/web.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

src/main/webapp/WEB-INF/Controller-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

src/main/webapp/WEB-INF/jsp/registrationConfirmation.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

src/main/webapp/index.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:

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

src/test/java/it/com/agical/experimental/controller/SeleniumTest.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:

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.

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.

Resources



(less...)

Pages

About
Events
Why

Categories

Agile
Automation
BDD
Clean code
Continuous delivery
Continuous deployment
Continuous integration
Cucumber
Culture
Design
DevOps
Executable specification
Git
Gradle
Guice
J2EE
JUnit
Java
Javascript
Kubernetes
Linux
Load testing
Maven
Mockito
New developers
Pair programming
PicoContainer
Presentation
Programming
Public speaking
Quality
React
Recruiting
Requirements
Scala
Selenium
Software craftsmanship
Software development
Spring
TDD
Teaching
Technical debt
Test automation
Tools
Web
Windows
eXtreme Programming

Authors

Thomas Sundberg
Adrian Bolboaca

Archives

Meta

rss RSS