Tutorial: Selenium continuous integration test

The project for this tutorial can be found at:

https://github.com/ericynt/blog-repo (development branch)

See the read me in the submodule for info. about how to run the project.

What does this project do?

  • clone the web application project that is going to be tested from a git repo.
  • download and unzip a version of Firefox that is compatible with the Selenium version that is used
  • build the project using a maven command
  • run the war that has been built in embedded Tomcat
  • run the Selenium test using the Firefox webdriver against the local Tomcat instance
  • close the browser and Tomcat

What are some of the advantages of setting up the test in this way?

It is very flexible. You don’t even need a repository manager. You can clone different branches. You can easily add more webdrivers and browser versions.

package com.eric;

import org.openqa.selenium.firefox.FirefoxBinary;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;

import java.io.File;

/**
 *
 */
public class FirefoxWebDriverFactory {
    static FirefoxDriver createFirefoxDriver () {
        String path = "\\target\\FirefoxPortable32\\FirefoxPortable.exe";
        String userDir = System.getProperty("user.dir");
        File file = new File(userDir + path);
        FirefoxBinary firefoxBinary = new FirefoxBinary(file);

        return new FirefoxDriver(firefoxBinary, new FirefoxProfile());
    }
}

package com.eric;

import org.junit.Assert;
import org.junit.Test;
import org.openqa.selenium.WebDriver;

/**
 *
 */
public class MyITCase {
   private WebDriver webDriver = FirefoxWebDriverFactory.createFirefoxDriver();

    @Test
    public void myTest () {
        webDriver.navigate().to("http://localhost:8080/webapp");
        Assert.assertEquals("Hello World!", webDriver.getTitle());
        webDriver.close();
    }
}

<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.eric</groupId>
        <artifactId>blog-projects</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>selenium-ci-tut</artifactId>
    <version>1.0.0</version>
    <packaging>war</packaging>

    <name>selenium-ci-tutorial</name>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-firefox-driver</artifactId>
            <version>2.53.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <filesets>
                        <fileset>
                            <directory>webapp</directory>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-scm-plugin</artifactId>
                <version>1.7</version>
                <executions>
                    <execution>
                        <id>git_clone</id>
                        <goals>
                            <goal>checkout</goal>
                        </goals>
                        <phase>initialize</phase>
                        <configuration>
                            <scmVersion>master</scmVersion>
                            <scmVersionType>branch</scmVersionType>
                            <checkoutDirectory>${project.basedir}/webapp</checkoutDirectory>
                            <workingDirectory>${project.basedir}/</workingDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.3.2</version>
                <executions>
                    <execution>
                        <id>build_webapp_war</id>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <phase>generate-resources</phase>
                        <configuration>
                            <executable>mvn</executable>
                            <arguments>
                                <argument>clean</argument>
                                <argument>install</argument>
                                <argument>-f</argument>
								<argument>webapp/pom.xml</argument>
                            </arguments>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>com.googlecode.maven-download-plugin</groupId>
                <artifactId>download-maven-plugin</artifactId>
                <version>1.3.0</version>
                <executions>
                    <execution>
                        <id>install_firefox</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>wget</goal>
                        </goals>
                        <configuration>
                            <url>http://www.firefox-usb.com/download/FirefoxPortable32-45.0.2.zip</url>
                            <unpack>true</unpack>
                            <outputDirectory>${basedir}/target</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <executions>
                    <execution>
                        <id>run-tomcat</id>
                        <goals>
                            <goal>run-war-only</goal>
                        </goals>
                        <phase>pre-integration-test</phase>
                        <configuration>
                            <port>8080</port>
                            <path>/webapp</path>
                            <fork>true</fork>
                            <warDirectory>${basedir}/webapp/target/hello-world-war-1.0.0</warDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.19.1</version>
                <executions>
                    <execution>
                        <id>run-it-tests</id>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Some final notes

  • the maven-failsafe-plugin picks up any Java test file that ends with ITCase or IT or starts with IT by default: http://maven.apache.org/surefire/maven-failsafe-plugin/examples/inclusion-exclusion.html
  • Selenium is very specific about which browser version is supported by which version of Selenium and which webdriver version: http://www.seleniumhq.org/about/platforms.jsp
  • I wouldn’t use PhantomJS for testing. Only maybe to automate something very trivial and it must run headless.
  • If you are using IntelliJ IDEA you can add the cloned project dir. to ‘excluded’ in Project Structure so it doesn’t get indexed every time you run the test
  • the tomcat7-maven-plugin requires that the packaging is war
  • as far as I can tell the download-maven-plugin is platform independent

What kind of bytecode does the Java compiler generate for a static initializer block?

As it turns out the compiler generates a second static constructor besides the default constructor. Look at the class and corresponding bytecode below:

/**
 *
 */
public class ClassWithStaticBlock {
    static int i;
    
    static {
        i = 2;
    }
}

You can lookup the bytecode mnemonics here.

// class version 49.0 (49)
// access flags 0x21
public class ClassWithStaticBlock {

  // compiled from: ClassWithStaticBlock.java

  // access flags 0x8
  static I i

  // access flags 0x1
  // generated default constructor
  public ()V
   L0
    LINENUMBER 4 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object. ()V
    RETURN
   L1
    LOCALVARIABLE this LClassWithStaticBlock; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x8
  // static constuctor
  static ()V
   L0
    LINENUMBER 8 L0
    ICONST_2 // push int value 2 on the operand stack
    PUTSTATIC ClassWithStaticBlock.i : I // pop value off the stack and assign it to the static field
   L1
    LINENUMBER 9 L1
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 0
}

Deploy MVC Spittr war from Spring in Action 4th edition in external Tomcat Server on Linux Mint using IntelliJ IDEA 14

Install Tomcat 7

sudo apt-get install tomcat7

Disable Tomcat running on start-up

sudo update-rc.d tomcat7 disable

Shutdown Tomcat

sudo /etc/init.d/tomcat7 stop

Import the chapter05 Spittr project in the same way as I explained here

Add the local Tomcat Server in the run configuration

Use the new Server
Add the Spittr war (exploded) as an artifact
Start Tomcat in the Application Server Tool Window

Go to http://localhost:8080

Howto run chapter01 sample code from Spring in Action 4th edition on the command line

Add this to build.gradle:

task copyToLib(type: Copy) {
    into "$buildDir/libs"
    from configurations.runtime
}

build.dependsOn(copyToLib)

This makes sure that the jar dependencies are copied to the lib directory,
when you build the project.

Save this in a batch or bash script file, in the Chapter_01 directory:

linux:

#!/bin/bash
cd knight/build/libs
java -cp *:. sia.knights.KnightMain

windows (haven’t tested it, but this should work):

cd knight\build\libs
java -cp *;. sia.knights.KnightMain

(* adds all the jars to the classpath and . adds the current dir. to the classpath)

command line