Enterprise Java

Making Web UI testing great again with Arquillian, Docker and Selenium (part 1)

Introduction to the Problem

Most of the time when you need to write functional tests/end-to-end tests for web UI, you end up by using Selenium, which it can consider the de-facto tool in Java world for web UI testing. I am sure you’ve already used it for these kind of tests.

But probably at the same time you’ve been faced on some the most common problems in functional testing, some related with Web UI testing and others not.

For example one of the major problems usually people find in functional tests are the preparation of the environment, to run the tests you need to boot up a server and deploy your application, then install/start the database, also maybe the cache system and so on with all the servers, leaving the user to install locally each of the service. Some errors could happen like installing incorrect version of the server used in production, reusing another local installation of the database which might not be the same version or for example running them in a different JDK version of the one used in production.

But also there are some other problems that are more specific to Web UI testing such as browser installation or configuration of WebDriver properties.

Fixing First Problem

To fix the first problem, the most easier solution you can think is using Docker containers and of course Docker compose since you can define and run multi-container Docker applications. So basically you define in docker-compose file, all the servers that you might need to run the tests so when you run tests you have all of them running and more important with a fixed version, so you can be sure that the tests are always run against a known/desired specific version of the servers, same JDK, … and not depending on what is installed in developers/CI machine.

But this approach has one problem. You need to specifically run docker-compose updocker-compose down. Of course you can automate this in your build script, which will solve the problem on CI environment, but if a developer wants to execute a test from IDE, let’s say for debugging, then he needs to be aware of that fact.

And this is what Arquillian Cube solves. Arquillian Cube is an Arquillian extensions that  uses docker-compose file to start and configure all the containers defined there, execute the tests and finally shutting down all of them. The good news is that since Arquillian works with JUnit (and TestNG and Spock), you can run the tests from the IDE without worrying about starting and stopping containers since Docker lifecycle is managed by Arquillian Cube.

So first part of the problem that is defining the test environment is fixed with Arquillian Cube. Let’s see how to fix the second one.

Fixing Second Problem

Selenium project provides a Docker images with Selenium standalone or Selenium node with browser (Firefox or Chrome) and a VNC server installed.

So it seems a perfect fit to fix the problem of having to install browsers with a concrete version or concrete configurations locally since you can use a docker image with a browser configured for the tests.

New Problems When Using Docker for Testing

And that’s cool, but it has some problems. The first one is that you need to create a docker-compose file specific for testing purposes, although this is not a bad thing per se, but it requires more management from dev part to maintain this file as well and of course repeat again and again in all the projects you want to use it, defining the browser to use and the VNC client image to get the recording for future inspection.

The second problem is the configuration of WebDriver. When running WebDriver against a remote browser, you need to set the location (IP) of the browser and configure the RemoteWebDriver accordantly with desired capabilities.

So again you have to write in all the tests the WebDriver configuration again and again. You can create a factory class that can be reused in all the projects, and it is good, but you still have one problem, some developers might use Docker machine so IP would not be static and might change every time, other might be using native Docker, and for example some phases of CI pipeline might run the tests against a remote fully environment like preproduction environment, so before executing tests you would need to specify manually the IP of container of Docker host.

And the third problem you’ll get is that you need to instruct WebDriver to open a page: webdriver.get(“http://www.google.com”);

The problem is that in this case the browser is inside the Docker infrastructre so you need to set the internal IP of the server container, so you don’t only need to know the Docker host IP for connecting the remote web driver but also the internal IP of the server container to open the page in remote browser using the
get method. And again this might be quite difficult to acquire in an automatic way.

But all these problems are solved when using the new integration between Arquillian Drone and Arquillian Cube.

Fixing New Problems

Arquillian Drone is an Arquillian extension that integrates Selenium WebDriver to Arquillian. This extension manages the configuration of the WebDriver so you don’t need to repeat it in all your tests, and also the lifecycle of the browser.

So as you can see this pair of extensions seems a perfect fit for solving these problems. Drone takes care of configuration meanwhile Cube takes care of configuring correctly the Selenium/VNC containers and starting and stopping them.

As you might see, you don’t need to worry about creating docker-compose file for testing purposes. You only need to create the one used for deploying, and Arquillian will take care of the rest.

Example

The first thing to do is create a project with required dependencies. For this example we are using Maven, but you can achieve the same using other build tools.

  
<?xml version="1.0" encoding="UTF-8"?>
<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>

    <groupId>org.lordofthejars.helloworld</groupId>
    <artifactId>dronecube</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencyManagement>
        <dependencies>
            <!-- Use BOMs to set same versions in all dependencies -->
            <dependency>
                <groupId>org.jboss.arquillian</groupId>
                <artifactId>arquillian-bom</artifactId>
                <version>1.1.11.Final</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.jboss.arquillian.extension</groupId>
                <artifactId>arquillian-drone-bom</artifactId>
                <version>2.0.0.Final</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.jboss.arquillian.selenium</groupId>
                <artifactId>selenium-bom</artifactId>
                <version>2.53.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!--  Use standalone mode in Arquillian (no @Deployment) -->
        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-standalone</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Cube dependencies -->
        <dependency>
            <groupId>org.arquillian.cube</groupId>
            <artifactId>arquillian-cube-docker</artifactId>
            <version>1.0.0.Alpha13</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.arquillian.cube</groupId>
            <artifactId>arquillian-cube-docker-drone</artifactId>
            <version>1.0.0.Alpha13</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.5.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- Drone dependencies -->
        <dependency>
            <groupId>org.jboss.arquillian.extension</groupId>
            <artifactId>arquillian-drone-webdriver-depchain</artifactId>
            <type>pom</type>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>

Things important to notice is that you are using BOM definitions for setting versions of the components. Then we set Arquillian Standalone dependency because our test is not going to have
@Deployment method since the deployment file is already created inside the Docker image used in the application. Finally Arquillian Cube and Arquillian Drone dependencies are added.

Next step is creating at src/test/resources a file called arquillian.xml which is used for configuring extensions.

  
 <?xml version="1.0"?>
<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns="http://jboss.org/schema/arquillian"
            xsi:schemaLocation="http://jboss.org/schema/arquillian
    http://jboss.org/schema/arquillian/arquillian_1_0.xsd">

    <extension qualifier="docker">
        <!-- Not required if native docker or only one docker machine installed -->
        <property name="machineName">dev</property>
        <!-- Not required if file is in root of classpath -->
        <property name="dockerContainersFile">docker-compose.yml</property>
    </extension>

</arquillian>

You can see that:

  • You need to specify the docker machine name where to start containers in case of using docker machine. If you are using native Docker then you don’t need to set this attribute.
  • You need to set a location relative to root folder of the project where docker-compose file is located. Note that you could use any other name.

You can customize WebDriver as well configuring Arquillian Drone (https://docs.jboss.org/author/display/ARQ/Drone),  but for this test the defaults are enough. Note that now the default browser is firefox.

IMPORTANT: if you are using native Linux Docker installation, comment the configuring line of machineName. If you are using docker machine and it is called different to dev, then adapt
machineName in arquillian.xml too.

Next step is creating the docker-compose file at root directory.

  
 helloworld:
  image: lordofthejars/helloworldgo
  ports:
    - "8080:80"

Simple compose file which defines only one container. This containers exposes the 80 port but then it is bound to port 8080. This container start a Go program listening to root context and returning
Hello World in HTML format.

And finally the test:

  
 package org.lordofthejars.cubedrone;

import org.arquillian.cube.CubeIp;
import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.junit.Arquillian;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

import java.net.MalformedURLException;
import java.net.URL;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(Arquillian.class)
public class HelloWorldTest {

    public static final int EXPOSED_PORT = 80;
    // Enrich with webdriver configured to connect to remote browser
    @Drone
    WebDriver webDriver;

    // Enrich with helloworld container ip
    @CubeIp(containerName = "helloworld")
    String ip;

    @Test
    public void shouldShowHelloWorld() throws MalformedURLException, InterruptedException {
        // Constructs url that browser should connect
        URL url = new URL("http", ip, EXPOSED_PORT, "/");
        // Typical test using WebDriver
        webDriver.get(url.toString());
        final String message = webDriver.findElement(By.tagName("h1")).getText();
        assertThat(message).isEqualTo("Hello World");
    }

}

There are some interesting parts in this test.

  • It is a standard Arquillian test in the sense it uses Arquillian runner.
  • Uses @Drone injection mechanism provided by Arquillian Drone to enrich test with a WebDriver configured to connect to remote browser.
  • Uses @CubeIp annotation to enrich test with the internal IP of the container helloworld. Since browser is running inside Docker host, we can use the internal IP for this purpose. Also it is important that you need to use the exposed port and not the bind port.
  • Everything else is managed by Arquillian Cube like the start and stop of the Docker containers(helloworld in this case) but also the ones containing the browser and the VNC client. If you put a debug point inside test method, and then execute a docker ps on a terminal, you’ll see that three containers are started, not just helloworld.
  • If after running the test you inspect target/reports/videos directory you will find the video recording of the test.

You can also see an screencast of this in action:

So as you can see using Arquillian Cube with Arquillian Drone makes your test and docker-compose file looks really neat.  Test only contains things related of the test and not about WebDriver configuration. Also your docker-compose looks clear, it only contains things related to business, not about testing.

In this post you’ve seen how to use Arquillian Cube + Arquillian Drone. In next one you’ll see the integration with Arquillian Graphene, which will simplify even more the test to just focusing testing and not on WebDriver calls.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button