JUnit 5 is the next generation of the well-known testing framework JUnit. Jupiter is the name given to the new programming and extension model provided by JUnit 5. The extension model of JUnit 5, it allows to incorporate extra capabilities for JUnit 5 tests. On the other hand, Selenium is a testing framework which allows to control local (Selenium WebDriver) or remote (Selenium Grid) browsers (e.g. Chrome, Firefox, and so on) programmatically to carry out automated testing of web applications. This documentation presents Selenium-Jupiter, a JUnit 5 extension aimed to provide seamless integration of Selenium (WebDriver and Grid) for JUnit 5 tests. Selenium-Jupiter is open source (Apache 2.0 license) and is hosted on GitHub.

Quick reference

Selenium-Jupiter has been built using the dependency injection capability provided by the extension model of JUnit 5. Thank to this feature, different types objects can be injected in JUnit 5 as methods or constructor parameters in @Test classes. Concretely, Selenium-Jupiter allows to inject subtypes of the WebDriver interface (e.g. ChromeDriver, FirefoxDriver, and so on).

Using Selenium-Jupiter is very easy. First, you need to import the dependency in your project (typically as test dependency). In Maven, it is done as follows:

<dependency>
        <groupId>io.github.bonigarcia</groupId>
        <artifactId>selenium-jupiter</artifactId>
        <version>2.2.0</version>
        <scope>test</scope>
</dependency>
Selenium-Jupiter 2.2.0 depends on selenium-java 3.13.0, webdrivermanager 2.2.4, appium java-client 6.1.0, and spotify docker-client 8.11.7. Therefore, by using the Selenium-Jupiter dependency, these libraries will be added as transitive dependencies to your project.

Then, you need to declare Selenium-Jupiter extension in your JUnit 5 test, simply annotating your test with @ExtendWith(SeleniumExtension.class). Finally, you need to include one or more parameters in your @Test methods (or constructor) whose types implements the WebDriver interface (e.g. ChromeDriver to use Chrome, FirefoxDriver for Firefox, and so for). That’s it. Selenium-Jupiter control the lifecycle of the WebDriver object internally, and you just need to use the WebDriver object in your test to drive the browser(s) you want. For example:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;

import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class ChromeAndFirefoxJupiterTest {

    @Test
    public void testWithOneChrome(ChromeDriver chromeDriver) {
        // Use Chrome in this test
    }

    @Test
    public void testWithFirefox(FirefoxDriver firefoxDriver) {
        // Use Firefox in this test
    }

    @Test
    public void testWithChromeAndFirefox(ChromeDriver chromeDriver,
            FirefoxDriver firefoxDriver) {
        // Use Chrome and Firefox in this test
    }

}
As of JUnit 5.1, extensions can be registered programmatically via @RegisterExtension or automatically via Java’s ServiceLoader. Both mechanisms can be used with Selenium-Jupiter.

The WebDriver subtypes supported by Selenium-Jupiter are the following:

  • ChromeDriver: Used to control Google Chrome browser.

  • FirefoxDriver: Used to control Firefox browser.

  • EdgeDriver: Used to control Microsoft Edge browser.

  • OperaDriver: Used to control Opera browser.

  • SafariDriver: Used to control Apple Safari browser (only possible in OSX El Capitan or greater).

  • HtmlUnitDriver: Used to control HtmlUnit (headless browser).

  • PhantomJSDriver: Used to control PhantomJS (headless browser).

  • InternetExplorerDriver: Used to control Microsoft Internet Explorer. Although this browser is supported, Internet Explorer is deprecated (in favor of Edge) and its use is highly discouraged.

  • RemoteWebDriver: Used to control remote browsers (Selenium Grid).

  • AppiumDriver: Used to control mobile devices (Android, iOS).

The browser to be used must be installed in the machine running the test beforehand (except in the case of RemoteWebDriver, in which the requirement is to known a Selenium Server URL). In the case of mobile devices (AppiumDriver), the emulator should be up and running in local or available in a Appium Server identified by an URL.

You can also inject WebDriver objects declaring them as constructor parameters, instead of as test method parameters. For example as follows:

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.chrome.ChromeDriver;

import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class ChromeInConstructorJupiterTest {

    ChromeDriver driver;

    public ChromeInConstructorJupiterTest(ChromeDriver driver) {
        this.driver = driver;
    }

    @Test
    public void testGlobalChrome() {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

This documentation contains a comprehensive collection of basic examples demonstrating the basic usage of Selenium-Jupiter in JUnit 5 tests using different types of browsers. All these examples are part of the test suite of Selenium-Jupiter and are executed on Travis CI.

Local browsers

Selenium WebDriver allows to control different types of browsers (such as Chrome, Firefox, Edge, and so on) programmatically using different programming languages. This is very useful to implement automated tests for web applications. Nevertheless, in order to use WebDriver, we need to pay a prize. For security reasons, the automated manipulation of a browser can only be done using native features of the browser. In practical terms, it means that a binary file must be placed in between the test using the WebDriver API and the actual browser. One the one hand, the communication between the WebDriver object and that binary is done using the (W3C WebDriver specification, formerly called JSON Wire Protocol. It consists basically on a REST service using JSON for requests and responses. On the other hand, the communication between the binary and the browser is done using native capabilities of the browser. Therefore, the general schema of Selenium WebDriver can be illustrated as follows:

webdriver general schema
Figure 1. WebDriver general scenario

From a tester point of view, the need of this binary component is a pain in the neck, since it should be downloaded manually for the proper platform running the test (i.e. Windows, Linux, Mac). Moreover, the binary version should be constantly updated. The majority of browsers evolve quite fast, and the corresponding binary file required by WebDriver needs to be also updated. The following picture shows a fine-grained diagram of the different flavor of WebDriver binaries and browsers:

webdriver particular schemas
Figure 2. WebDriver scenario for Chrome, Firefox, Opera, PhantomJS, Edge, and Internet Explorer

Concerning Java, in order to locate these drivers, the absolute path of the binary controlling the browser should be exported in a given environment variable before creating a WebDriver instance, as follows:

System.setProperty("webdriver.chrome.driver", "/path/to/chromedriver");
System.setProperty("webdriver.opera.driver", "/path/to/operadriver");
System.setProperty("webdriver.ie.driver", "C:/path/to/IEDriverServer.exe");
System.setProperty("webdriver.edge.driver", "C:/path/to/MicrosoftWebDriver.exe");
System.setProperty("phantomjs.binary.path", "/path/to/phantomjs");
System.setProperty("webdriver.gecko.driver", "/path/to/geckodriver");

In order to simplify the life of Java WebDriver users, in March 2015 the utility WebDriverManager was first released. WebDriverManager is a library which automates all this process (download the proper binary and export the proper variable) for Java in runtime. The WebDriverManager API is quite simple, providing a singleton object for each of the above mentioned browsers:

WebDriverManager.chromedriver().setup();
WebDriverManager.firefoxdriver().setup();
WebDriverManager.operadriver().setup();
WebDriverManager.phantomjs().setup();
WebDriverManager.edgedriver().setup();
WebDriverManager.iedriver().setup();;

The solution implemented by WebDriverManager is today supported by similar tools for other languages, such as webdriver-manager for Node.js or WebDriverManager.Net for .NET.

On September 2017, a new major version of the well-know testing JUnit framework was released. This leads to Selenium-Jupiter, which can be seen as the natural evolution of WebDriverManager for JUnit 5 tests. Internally, Selenium-Jupiter is built using two foundations:

  1. It uses WebDriverManager to manage the binaries requires by WebDriver.

  2. It uses the dependency injection feature of the extension model of JUnit 5 to inject WebDriver objects within @Test methods.

All in all, using Selenium WebDriver to control browsers using Java was never that easy. Using JUnit 5 and Selenium-Jupiter, you simply need to declare the flavor of browser you want to use in your test method (or constructor) and use it.

Chrome

The following example contains a simple usage of Chrome in JUnit 5. The complete source code of this test is hosted on GitHub. Notice that this class contains two tests (methods annotated with @Test). The first one (testWithOneChrome) declares just one ChromeDriver parameter, and therefore this test controls a single Chrome browser. On the other hand, the second @Test (testWithTwoChromes) declares two different ChromeDriver parameters, and so, it controls two Chrome browsers.

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.chrome.ChromeDriver;

import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class ChromeJupiterTest {

    @Test
    public void testWithOneChrome(ChromeDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

    @Test
    public void testWithTwoChromes(ChromeDriver driver1, ChromeDriver driver2) {
        driver1.get("http://www.seleniumhq.org/");
        driver2.get("http://junit.org/junit5/");
        assertThat(driver1.getTitle(), startsWith("Selenium"));
        assertThat(driver2.getTitle(), equalTo("JUnit 5"));
    }

}

Firefox

The following test uses Firefox as browser(s). To that aim, @Test methods simply need to include FirefoxDriver parameters.

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.firefox.FirefoxDriver;

import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class FirefoxJupiterTest {

    @Test
    public void testWithOneFirefox(FirefoxDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

    @Test
    public void testWithTwoFirefoxs(FirefoxDriver driver1,
            FirefoxDriver driver2) {
        driver1.get("http://www.seleniumhq.org/");
        driver2.get("http://junit.org/junit5/");
        assertThat(driver1.getTitle(), startsWith("Selenium"));
        assertThat(driver2.getTitle(), equalTo("JUnit 5"));
    }

}

Edge

The following example uses one Edge browser. This test should be executed on a Windows machine with Edge.

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.edge.EdgeDriver;

import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class EdgeJupiterTest {

    @Test
    void edgeTest(EdgeDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}
The required version of MicrosoftWebDriver.exe depends on the version on Edge to be used (more info here). By default, WebDriverManager downloads and uses the latest version of the binaries. Nevertheless, a concrete version can be fixed. Take a look to the advance examples section to find out how to setup the different options of WebDriverManager.

Opera

Are you one of the few using Opera? No problem, you can still make automated tests with JUnit 5, WebDriver, and Selenium-Jupiter, as follows:

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.opera.OperaDriver;

import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class OperaJupiterTest {

    @Test
    public void test(OperaDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

Safari

You can also use Safari in conjunction with Selenium-Jupiter. Take into account that SafariDriver requires Safari 10 running on OSX El Capitan or greater.

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.safari.SafariDriver;

import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class SafariJupiterTest {

    @Test
    public void test(SafariDriver driver) {
        driver.get("http://www.seleniumhq.org/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

PhamtomJS

PhamtomJS is a headless browser (i.e. a browser without GUI), and it can be convenient for different types of tests. The following example demonstrates how to use PhamtomJS with Selenium-Jupiter.

import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.phantomjs.PhantomJSDriver;

import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class PhantomjsJupiterTest {

    @Test
    public void test(PhantomJSDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getPageSource(), notNullValue());
    }

}

HtmlUnit

HtmlUnit is another headless browser that can be used easily in a Jupiter test, for example like this:

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;

import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class HtmlUnitJupiterTest {

    @Test
    public void test(HtmlUnitDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

Docker browsers

As of version 2.0.0, Selenium-Jupiter allows to ask for browsers in Docker containers. The only requirement to use this feature is to install Docker Engine in the machine running the tests. Internally, Selenium-Jupiter uses a docker-client and different Docker images for browsers, namely:

  • Stable versions of Docker browser, provided by Selenoid.

  • Beta and nightly versions of Docker browser, provided by ElasTest.

  • Browsers in Android devices, provided by Budi Utomo.

As shown in the following section, the mode of operation is similar to local browser. We simply asks for browsers in Docker simply declaring parameters in our @Test methods, and Selenium-Jupiter will make magic for us: it downloads the proper Docker image for the browser, start it, and instantiate the object of type WebDriver or RemoteWebDriver to control the browser from our test. The annotation @DockerBrowser need to be declared in the parameter to mark the WebDriver object as a browser in Docker.

Chrome

The following example contains a simple test example using Chrome browsers in Docker. Check out the code here. As you can see, the first @Test method (called testChrome) declares a parameter of type RemoteWebDriver. This parameter is annotated with @DockerBrowser. This annotation requires to set the browser type, in this case CHROME. If no version is specified, then the latest version of the browser will be used. This feature is known as evergreen Docker browsers, and it is implementing by consuming the REST API of Docker Hub, asking for the list of Selenoid browsers. On the other hand, the second @Test (called testChromeWithVersion) a fixed version is set, in this case 67.0.

import static io.github.bonigarcia.BrowserType.CHROME;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.remote.RemoteWebDriver;

import io.github.bonigarcia.DockerBrowser;
import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class DockerChromeJupiterTest {

    @Test
    public void testChrome(
            @DockerBrowser(type = CHROME) RemoteWebDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

    @Test
    public void testChromeWithVersion(
            @DockerBrowser(type = CHROME, version = "67.0") RemoteWebDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

In this other example, wildcards are used to set the browser version. In the first @Test (method testLatestChrome), we use the literal latest to mark the use of the latest version (in fact the use of latest is exactly the same that not declaring the version attribute). The second @Test (method testFormerChrome) sets the version as latest-1. This should be read as latest version minus one, in other words, the previous version to the stable version at the time of the test execution. Notice that the concrete versions for both test will evolve in time, since new versions are released constantly. All in all, you have the certainty of using the latest versions of the browser without any kind of extra configuration nor maintenance of the underlying infrastructure.

Moreover, in third (testBetaChrome) and fourth (testUnstableChrome) test beta and unstable (i.e. development) versions are used. This feature is available both for Chrome and Firefox, thanks to the Docker images provided by the ElasTest project.

import static io.github.bonigarcia.BrowserType.CHROME;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.WebDriver;

import io.github.bonigarcia.DockerBrowser;
import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class DockerChromeLatestJupiterTest {

    @Test
    public void testLatestChrome(
            @DockerBrowser(type = CHROME, version = "latest") WebDriver driver) {
        // Use stable version of Chrome in this test
    }

    @Test
    public void testFormerChrome(
            @DockerBrowser(type = CHROME, version = "latest-1") WebDriver driver) {
        // Use previous to stable version of Chrome in this test
    }

    @Test
    public void testBetaChrome(
            @DockerBrowser(type = CHROME, version = "beta") WebDriver driver) {
        // Use beta version of Chrome in this test
    }

    @Test
    public void testUnstableChrome(
            @DockerBrowser(type = CHROME, version = "unstable") WebDriver driver) {
        // Use development version of Chrome in this test
    }

}
The label latest-* is supported, where * is a number for a former version to the current stable. For instance, latest-2 means the two previous version to the stable (for instance, if at the time of running a test the latest version is 63.0, latest-2 will mean version 61.0).

Firefox

The use of Firefox is equivalent. With respect to the previous example, it simply change the type of browser. Versioning works exactly the same.

import static io.github.bonigarcia.BrowserType.FIREFOX;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.remote.RemoteWebDriver;

import io.github.bonigarcia.DockerBrowser;
import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class DockerFirefoxJupiterTest {

    @Test
    public void testLatest(
            @DockerBrowser(type = FIREFOX) RemoteWebDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

    @Test
    public void testVersion(
            @DockerBrowser(type = FIREFOX, version = "60") RemoteWebDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}
Notice that the version of the second test is simply 60. The actual version of the image is 60.0, but Selenium-Jupiter supposes that version is .0 if not specified.

Opera

Again, the use of Opera browsers in Docker is the same, simply changing the browser type to OPERA.

import static io.github.bonigarcia.BrowserType.OPERA;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.remote.RemoteWebDriver;

import io.github.bonigarcia.DockerBrowser;
import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class DockerOperaJupiterTest {

    @Test
    public void testOpera(@DockerBrowser(type = OPERA) RemoteWebDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

Android

The use of browser devices in Selenium-Jupiter is straightforward. Simply change the browser type to ANDROID as follows.

import static io.github.bonigarcia.BrowserType.ANDROID;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.remote.RemoteWebDriver;

import io.github.bonigarcia.DockerBrowser;
import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class DockerAndroidJupiterTest {

    @Test
    public void testAndroid(
            @DockerBrowser(type = ANDROID) RemoteWebDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

The supported Android devices are summarized in the following table:

Table 1. Android devices in Selenium-Jupiter
Android version API level Browser name

5.0.1

21

browser

5.1.1

22

browser

6.0

23

chrome

7.0

24

chrome

7.1.1

25

chrome

Moreover, the type of devices are the following:

Table 2. Android devices types in Selenium-Jupiter
Type Device name

Phone

Samsung Galaxy S6

Phone

Nexus 4

Phone

Nexus 5

Phone

Nexus One

Phone

Nexus S

Tablet

Nexus 7

By default, the latest version of Android (7.1.1) using Chrome in a Samsung Galaxy S6. These values can be changed using the configuration capabilities or by means of the the following parameters in the @DockerBrowser annotation

import static io.github.bonigarcia.BrowserType.ANDROID;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.remote.RemoteWebDriver;

import io.github.bonigarcia.DockerBrowser;
import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class DockerAndroidCustomJupiterTest {

    @Test
    public void testAndroid(@DockerBrowser(type = ANDROID, version = "5.0.1",
            deviceName = "Nexus S", browserName = "browser") RemoteWebDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

Remote sessions (VNC)

Selenium-Jupiter allows to track the evolution of our browsers in Docker using Virtual Network Computing (VNC) sessions. This feature is disabled by default, but it can activated using the configuration key sel.jup.vnc to true (more info on section Configuration).

When it is activated, the VNC URL of a browser in Docker is printed in the test log, concretely using the DEBUG level (see example below). Simply copying and pasting that URL in a real browser we can take a look to the browser while the test is being executed. We can even interact with the Docker browser.

2018-03-31 17:07:03 [main] INFO  i.g.b.handler.DockerDriverHandler - VNC URL (copy and paste in a browser navigation bar to interact with remote session)
2018-03-31 17:07:03 [main] INFO  i.g.b.handler.DockerDriverHandler - http://192.168.99.100:32769/vnc.html?host=192.168.99.100&port=32768&path=vnc/aa39e2562bf0f58bfbad0924d22ca958&resize=scale&autoconnect=true&password=selenoid
vnc chrome in docker
Figure 3. Example of VNC session of Chrome (desktop)
vnc chrome in android docker
Figure 4. Example of VNC session of Chrome (Android)
In addition to log the VNC URL, as of Selenium-Jupiter 2.1.0, the value of this URL is exported as Java property as vnc.session.url (i.e. System.setProperty("vnc.session.url", vncUrl);).

Recordings

Selenium-Jupiter allows to record the sessions of browsers in Docker. This capability is not activated by default, but it activated simply setting the configuration key sel.jup.recording to true (see section Configuration for further details about configuration).

This way, a recording in MP4 format will be stored at the end of the test which uses one or several browsers in Docker. The output folder in which the recording is stored is configured by means of the configuration key sel.jup.output.folder, whose default value is . (i.e. the current folder in which the test is executed). The following picture shows an example of recording.

recording chrome in docker
Figure 5. Example of recording played in VLC

Performance tests

Another important new feature of browsers in Docker is the possibility of asking for many of them by the same test. This can be used to implement performance tests in a seamless way. To use this feature, we need into account two aspects. First of all, the attribute size of the annotation @DockerBrowser should be declared. This numeric value sets the number of browsers demanded by the test. Second, the test declares a List<RemoteWebDriver> (or List<WebDriver>). For example as follows:

import static io.github.bonigarcia.BrowserType.CHROME;
import static java.lang.invoke.MethodHandles.lookup;
import static java.util.concurrent.Executors.newFixedThreadPool;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.slf4j.LoggerFactory.getLogger;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.slf4j.Logger;

import io.github.bonigarcia.DockerBrowser;
import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class PerformenceDockerChromeJupiterTest {

    static final int NUM_BROWSERS = 3;

    final Logger log = getLogger(lookup().lookupClass());

    @Test
    public void testPerformance(
            @DockerBrowser(type = CHROME, version = "67.0", size = NUM_BROWSERS) List<RemoteWebDriver> driverList)
            throws InterruptedException {

        ExecutorService executorService = newFixedThreadPool(NUM_BROWSERS);
        CountDownLatch latch = new CountDownLatch(NUM_BROWSERS);

        driverList.forEach((driver) -> {
            executorService.submit(() -> {
                try {
                    log.info("Session id {}",
                            ((RemoteWebDriver) driver).getSessionId());
                    driver.get(
                            "https://bonigarcia.github.io/selenium-jupiter/");
                    assertThat(driver.getTitle(), containsString(
                            "JUnit 5 extension for Selenium"));
                } finally {
                    latch.countDown();
                }
            });
        });

        latch.await(50, SECONDS);
        executorService.shutdown();
    }

}

This example requires a list of 3 Chrome browsers in Docker. Then, it executed in parallel a given logic. Notice that if the number of browsers is high, the CPU and memory consumption of the test running the machine will increase accordingly.

Interactive mode

As of version 2.1.0, Selenium-Jupiter can used interactively from the shell to get the VNC session of Docker browser. There are two ways of using this feature:

  • Directly from the source code, using Maven. The command to be used is mvn exec:java -Dexec.args="browserName <version>". For instance:

$ mvn exec:java -Dexec.args="chrome beta"
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building selenium-jupiter 2.1.2-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ selenium-jupiter ---
[INFO] Using SeleniumJupiter to execute chrome beta in Docker
[INFO] Getting browser image list from Docker Hub
[INFO] Using CHROME version beta
[INFO] Pulling Docker image elastestbrowsers/chrome:beta ... please wait
[INFO] Starting Docker container aerokube/selenoid:1.6.0
mar 31, 2018 5:50:15 PM org.openqa.selenium.remote.ProtocolHandshake createSession
INFORMATION: Detected dialect: OSS
[INFO] Starting Docker container psharkey/novnc:3.3-t6
[INFO] Session id 957187e9f3f99312ebd2c81546750643
[INFO] VNC URL (copy and paste in a browser navigation bar to interact with remote session)
[INFO] http://192.168.99.100:32775/vnc.html?host=192.168.99.100&port=32774&path=vnc/957187e9f3f99312ebd2c81546750643&resize=scale&autoconnect=true&password=selenoid
[INFO] Press ENTER to exit

[INFO] Stopping Docker container aerokube/selenoid:1.6.0
[INFO] Stopping Docker container psharkey/novnc:3.3-t6
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:10 min
[INFO] Finished at: 2018-03-31T17:51:15+02:00
[INFO] Final Memory: 27M/390M
[INFO] ------------------------------------------------------------------------
  • Using Selenium-Jupiter as a fat-jar. This jar can be created using the command mvn compile assembly:single from the source code, and then java -jar selenium-jupiter.jar browserName <version>. For instance:

$ java -jar selenium-jupiter-2.1.2-SNAPSHOT-jar-with-dependencies.jar firefox
[INFO] Using SeleniumJupiter to execute firefox (latest) in Docker
[INFO] Getting browser image list from Docker Hub
[INFO] Using FIREFOX version 59.0 (latest)
[INFO] Pulling Docker image selenoid/vnc:firefox_59.0 ... please wait
[INFO] Starting Docker container aerokube/selenoid:1.6.3
mar 31, 2018 5:53:51 PM org.openqa.selenium.remote.ProtocolHandshake createSession
INFORMATION: Detected dialect: W3C
[INFO] Starting Docker container psharkey/novnc:3.3-t6
[INFO] Session id 651fcf54-183e-4732-8fae-6a525140f6f0
[INFO] VNC URL (copy and paste in a browser navigation bar to interact with remote session)
[INFO] http://192.168.99.100:32777/vnc.html?host=192.168.99.100&port=32776&path=vnc/651fcf54-183e-4732-8fae-6a525140f6f0&resize=scale&autoconnect=true&password=selenoid
[INFO] Press ENTER to exit

[INFO] Stopping Docker container psharkey/novnc:3.3-t6
[INFO] Stopping Docker container aerokube/selenoid:1.6.3
As of version 2.2.0, the parameter browserName can be used to select an android device. In this case, a couple of addition parameter can be specified: browserNameInAdroid for the browser in Android (chrome or browser) and deviceName for the device type (Samsung Galaxy S6, Nexus 4, Nexus 5, etc.).

Remote browsers

Selenium-Jupiter also supports remote browsers, using a Selenium Grid server. To that aim, a couple of custom annotations can be used:

  • DriverUrl (parameter-level or field-level): Annotation used to identify the URL value needed to instantiate a RemoteWebDriver object. This URL can be setup using the configuration key sel.jup.selenium.server.url. This value can be used to use browsers from cloud providers, such as Saucelabs or BrowserStack.

  • DriverCapabilities (parameter-level or field-level): Annotation to configure the desired capabilities (WebDriver’s object DesiredCapabilities).

Using capabilities

The annotation @DriverCapabilities is used to specify WebDriver capabilities (i.e. type browser, version, platform, etc.). These capabilities are typically used for Selenium Grid tets (i.e. tests using remote browsers). To that aim, an Selenium Hub (also known as Selenium Server) should be up an running, and its URL should known. This URL will be specified using the Selenium-Jupiter annotation @DriverUrl.

The following example provides a complete example about this. As you can see, in the test setup (@BeforeAll) a Selenium Grid is implemented, first starting a Hub (a.k.a. Selenium Server), and then a couple of nodes (Chrome a Firefox) are registered in the Hub. Therefore, remote test using RemoteWebDriver can be executed, simply pointing to the Hub (whose URL in this case is http://localhost:4444/wd/hub in this example) and selecting the browser to be used using the Capabilities.

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.openqa.selenium.remote.DesiredCapabilities.firefox;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.RemoteWebDriver;

import io.github.bonigarcia.DriverCapabilities;
import io.github.bonigarcia.DriverUrl;
import io.github.bonigarcia.SeleniumExtension;
@ExtendWith(SeleniumExtension.class)
public class RemoteWebDriverJupiterTest {

    @DriverUrl
    String url = "http://localhost:4444/wd/hub";

    @DriverCapabilities
    Capabilities capabilities = firefox();

    @Test
    void testWithRemoteChrome(@DriverUrl("http://localhost:4445/wd/hub")
            @DriverCapabilities("browserName=chrome") RemoteWebDriver driver) {
        exercise(driver);
    }

    @Test
    void testWithRemoteFirefox(RemoteWebDriver driver) {
        exercise(driver);
    }

    void exercise(WebDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

The following class contains an example which uses Chrome as browser and capabilities defined using @DriverCapabilities. Concretely, this example uses the mobile emulation feature provided out of the box by Chrome (i.e. render the web page using small screen resolutions to emulate smartphones).

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.openqa.selenium.chrome.ChromeOptions.CAPABILITY;
import static org.openqa.selenium.remote.DesiredCapabilities.chrome;

import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.remote.DesiredCapabilities;

import io.github.bonigarcia.DriverCapabilities;
import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class ChromeWithGlobalCapabilitiesJupiterTest {

    @DriverCapabilities
    DesiredCapabilities capabilities = chrome();
    {
        Map<String, String> mobileEmulation = new HashMap<String, String>();
        mobileEmulation.put("deviceName", "Nexus 5");
        Map<String, Object> chromeOptions = new HashMap<String, Object>();
        chromeOptions.put("mobileEmulation", mobileEmulation);
        capabilities.setCapability(CAPABILITY, chromeOptions);
    }

    @Test
    void chromeTest(ChromeDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

Mobile testing with Appium

The annotation @DriverCapabilities can be also used to specify the desired capabilities to create an instances of AppiumDriver to drive mobile devices (Android or iOS). If not @DriverUrl is specified, Selenium-Jupiter will start automatically an instance of Appium Server (by default in port 4723) in the localhost after each test execution (this server is shutdown before each test). For example:

import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;

import io.appium.java_client.AppiumDriver;
import io.github.bonigarcia.DriverCapabilities;
import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class AppiumChromeJupiterTest {

    @Test
    void testWithAndroid(
            @DriverCapabilities({ "browserName=chrome",
                    "deviceName=Android" }) AppiumDriver<WebElement> driver)
            throws InterruptedException {

        String context = driver.getContext();
        driver.context("NATIVE_APP");
        driver.findElement(By.id("com.android.chrome:id/terms_accept")).click();
        driver.findElement(By.id("com.android.chrome:id/negative_button"))
                .click();
        driver.context(context);

        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertTrue(driver.getTitle().contains("JUnit 5 extension"));
    }

}

We can also specify a custom Appium Server URL changing the value of @DriverUrl, at field-level or parameter-level:

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;

import io.appium.java_client.AppiumDriver;
import io.github.bonigarcia.DriverCapabilities;
import io.github.bonigarcia.DriverUrl;
import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class AppiumWithGlobalOptionsChromeJupiterTest {

    @DriverUrl
    String url = "http://localhost:4723/wd/hub";

    @DriverCapabilities
    DesiredCapabilities capabilities = new DesiredCapabilities();
    {
        capabilities.setCapability("browserName", "chrome");
        capabilities.setCapability("deviceName", "Samsung Galaxy S6");
    }

    @Test
    void testWithAndroid(AppiumDriver<WebElement> driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

Advanced features

Using options

So far, we have discovered how to use different local browsers (Chrome, Firefox, Edge, Opera, Safari, PhamtomJS, HtmlUnit), Docker browsers (Chrome, Firefox, Opera), or even remote browsers with Selenium Grid. In any case, the default options are used. Nevertheless, if you have used intensively Selenium WebDriver, different questions might come to your mind:

  • What if I need to specify options (e.g. ChromeOptions, FirefoxOptions, etc) to my WebDriver object?

  • What if need to specify desired capabilities (e.g. browser type, version, platform)?

In order to support the advance features of Selenium WebDriver, Selenium-Jupiter provides several annotations aimed to allow a fine-grained control of the WebDriver object instantiation. These annotations are:

  • Options (field-level): Annotation to configure options (e.g. ChromeOptions for Chrome, FirefoxOptions for Firefox, EdgeOptions for Edge, OperaOptions for Opera, and SafariOptions for Safari).

  • Arguments (parameter-level) : Used to add arguments to the options.

  • Preferences (parameter-level) : Used to set preferences to the options.

  • Binary (parameter-level) : Used to set the location of the browser binary.

  • Extensions (parameter-level) : User to add extensions to the browser.

The annotations marked as parameter-level are applied to a single WebDriver parameter. The annotations marked as field-level are applied globally in a test class.

The following example shows how to specify options for Chrome. In the first test (called headlessTest), we are setting the argument --headless, used in Chrome to work as a headless browser. In the second test (webrtcTest), we are using two different arguments: --use-fake-device-for-media-stream and --use-fake-ui-for-media-stream, used to fake user media (i.e. camera and microphone) in WebRTC applications. In the third test (extensionTest), we are adding an extension to Chrome using the @Extensions annotation. The value of this field is an extension file that will be searched: i) using value as its relative/absolute path; ii) using value as a file name in the project classpath.

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;

import io.github.bonigarcia.Arguments;
import io.github.bonigarcia.Extensions;
import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class ChromeWithOptionsJupiterTest {

    @Test
    void headlessTest(@Arguments("--headless") ChromeDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

    @Test
    void webrtcTest(@Arguments({ "--use-fake-device-for-media-stream",
            "--use-fake-ui-for-media-stream" }) ChromeDriver driver) {
        driver.get(
                "https://webrtc.github.io/samples/src/content/devices/input-output/");
        assertThat(driver.findElement(By.id("video")).getTagName(),
                equalTo("video"));
    }

    @Test
    void extensionTest(@Extensions("hello_world.crx") ChromeDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

As introduced before, this annotation @Options can be used also at field-level, as shown in this other example. This test is setting to true the Firefox preferences media.navigator.streams.fake and media.navigator.permission.disabled, used also for WebRTC.

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.By;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;

import io.github.bonigarcia.Options;
import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class FirefoxWithGlobalOptionsJupiterTest {

    @Options
    FirefoxOptions firefoxOptions = new FirefoxOptions();
    {
        // Flag to use fake media for WebRTC user media
        firefoxOptions.addPreference("media.navigator.streams.fake", true);

        // Flag to avoid granting access to user media
        firefoxOptions.addPreference("media.navigator.permission.disabled",
                true);
    }

    @Test
    public void webrtcTest(FirefoxDriver driver) {
        driver.get(
                "https://webrtc.github.io/samples/src/content/devices/input-output/");
        assertThat(driver.findElement(By.id("video")).getTagName(),
                equalTo("video"));
    }

}

Template tests

Selenium-Jupiter takes advantage on the standard feature of JUnit 5 called test templates. Test templates can be seen as an special kind of parameterized tests, in which the test is executed several times according to the data provided by some extension. In our case, the extension is Selenium-Jupiter itself, and the test template is configured using a custom file in JSON called browsers scenario.

Let’s see some examples. Consider the following test. A couple of things are new in this test. First of all, instead of declaring the method with the usual @Test annotation, we are using the JUnit 5’s annotation @TestTemplate. With this we are saying to JUnit that this method is not a regular test case but a template. Second, the parameter type of the method templateTest is WebDriver. This is the generic interface of Selenium WebDriver, and the concise type (i.e. ChromeDriver, FirefoxDriver, RemoteWebDriver, etc.) will be determined by Selenium-Jupiter in runtime.

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.WebDriver;

import io.github.bonigarcia.SeleniumExtension;
import io.github.bonigarcia.SeleniumJupiter;

@ExtendWith(SeleniumExtension.class)
public class TemplateTest {

    @TestTemplate
    void templateTest(WebDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

The last piece we need in this test template is what we call browser scenario. As introduced before, this scenario is defined in a JSOn file following a simple notation.

The path of the JSON browser scenario is established in the configuration key called sel.jup.browser.template.json.file. By default, this key has the value classpath:browsers.json. This means that the JSON scenario is defined in a file called browsers.json located in the classpath (see section Configuration for further details about configuration).

If the configuration key sel.jup.browser.template.json.file do not start with the word classpath:, the file will be searched using relative of absolute paths.

Now imagine that the content of the file browsers.json is as follows:

{
   "browsers": [
      [
         {
            "type": "chrome-in-docker",
            "version": "latest"
         }
      ],
      [
         {
            "type": "chrome-in-docker",
            "version": "latest-1"
         }
      ],
      [
         {
            "type": "chrome-in-docker",
            "version": "beta"
         }
      ],
      [
         {
            "type": "chrome-in-docker",
            "version": "unstable"
         }
      ]
   ]
}

When we execute the template test, in this case we will have four actual tests: the first using the latest version of Chrome, the second using the previous to stable version of Chrome (latest-1), the third using the beta version of Chrome (beta), and another test using the development version of Chrome (unstable). For instance, if we run the test in Eclipse, we will get the following output:

test template 01
Figure 6. Example of test template execution in Eclipse

Generally speaking, a browser within the JSON scenario is defined using the following parameters:

  • type: Type of browsers. The accepted values are:

    • chrome: For local Chrome browsers.

    • firefox: For local Firefox browsers.

    • edge: For local Edge browsers.

    • iexplorer: For local Internet Explorer browsers.

    • opera: For local Opera browsers.

    • safari: For local Safari browsers.

    • appium: For local mobile emulated devices.

    • phantomjs: For local PhtanomJS headless browsers.

    • chrome-in-docker: For Chrome browsers in Docker.

    • firefox-in-docker: For Firefox browsers in Docker.

    • opera-in-docker: For Opera browsers in Docker.

    • android: For web browsers in Android devices in Docker containers.

  • version: Optional value for the version. Wildcard for latest versions (latest, latest-1, etc) are accepted. Concrete versions are also valid (e.g. 63.0, 57.0, etc., depending of the browser). Beta and unstable (i.e. development) versions for Chrome and Firefox are also supported (using the labels beta and unstable labels respectively).

  • browserName: As of version 2.2.0, when the type is android device, the parameter browserName can be used to select the browser in Android (chrome or browser)

  • deviceName: Also for android type, the device type can be specified (Samsung Galaxy S6, Nexus 4, Nexus 5, etc.).

Finally, more than one parameters can be defined in the test template. For instance, consider the following test in which a couple of WebDriver parameters are declared in the test template.

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.WebDriver;

import io.github.bonigarcia.SeleniumExtension;
import io.github.bonigarcia.SeleniumJupiter;

@ExtendWith(SeleniumExtension.class)
public class TemplateTwoBrowsersTest {

    @TestTemplate
    void templateTest(WebDriver driver1, WebDriver driver2) {
        driver1.get("https://bonigarcia.github.io/selenium-jupiter/");
        driver2.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver1.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
        assertThat(driver2.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

The JSON scenario should be defined accordingly. Each browser array in this case (for each test template execution) should declare two browsers. For instance, using the following JSON scenario, the first execution will be based on Chrome in Docker (first parameter) and Firefox in Docker (second parameter); and the second execution will be based on a local Chrome (first parameter) and the headless browser PhantomJS (second parameter).

{
   "browsers": [
      [
         {
            "type": "chrome-in-docker"
         },
         {
            "type": "firefox-in-docker"
         }
      ],
      [
         {
            "type": "chrome"
         },
         {
            "type": "phantomjs"
         }
      ]
   ]
}

If we execute this test using in GUI, the JUnit tab shows two tests executed with the values defined in the JSON scenario.

test template 02
Figure 7. Example of test template execution (with two parameters) in Eclipse

As of version 2.2.0, Selenium-Jupiter allows to configure the browser scenario programmatically using the JUnit 5 @RegisterExtension annotation. To that aim, the method addBrowsers of the SeleniumExtension instance is used to add different browser(s) to the scenario. In the following example the test is executed twice, one using Chrome and the second using Firefox.

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.openqa.selenium.WebDriver;

import io.github.bonigarcia.BrowserBuilder;
import io.github.bonigarcia.BrowsersTemplate.Browser;
import io.github.bonigarcia.SeleniumExtension;

public class TemplateRegisterTest {

    @RegisterExtension
    static SeleniumExtension seleniumExtension = new SeleniumExtension();

    @BeforeAll
    static void setup() {
        Browser chrome = BrowserBuilder.chrome().build();
        Browser firefox = BrowserBuilder.firefox().build();
        seleniumExtension.addBrowsers(chrome);
        seleniumExtension.addBrowsers(firefox);
    }

    @TestTemplate
    void templateTest(WebDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

Generic driver

As of version 2.1.0, Selenium-Jupiter allows to use a configurable WebDriver object. This generic driver is declared as usual (i.e. as test method or constructor parameter) using the type RemoteWebDriver or WebDriver. The concrete type of browser to be used is established using the configuration key sel.jup.default.browser. The default value for this key is chrome-in-docker. All the values used in the template test defined in the previous section (i.e. chrome, firefox, edge, chrome-in-docker, firefox-in-docker, android, etc.) can be used also to define the type of browser in this mode.

For instance, the following test, if no additional configuration is done, will use Chrome in Docker as browser:

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.WebDriver;

import io.github.bonigarcia.SeleniumExtension;

@ExtendWith(SeleniumExtension.class)
public class GenericTest {

    @Test
    void genericTest(WebDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

If the resolution of this browser finishes with exception (for instance, when executing the test in a host without Docker), a list of browser fallback will be used. This list is managed using the configuration key sel.jup.default.browser.fallback. By default, this key has the value chrome,firefox,safari,edge,phantomjs, meaning that the first fallback browser is a local Chrome, then local Firefox, then local Safari, then local Edge, and finally PhantomJS (headless browser).

The version of the generic browser (in case of Docker browsers) is managed with the key sel.jup.default.version (latest by default). The versions of the fallback browsers can be also managed, this time using the configuration key sel.jup.default.browser.fallback.version.

Integration with Jenkins

Selenium-Jupiter provides seamless integration with Jenkins through one of its plugins: the Jenkins attachment plugin. The idea is to provide the ability to attache output files (typically PNG screenshots and MP4 recordings of Docker browsers), and keep these files attached to the job execution. This is done in Selenium-Jupiter setting the configuration key sel.jup.output.folder to an special value: surefire-reports.

When this configuration key is configured with that value, Selenium-Jupiter will store the generated files in the proper folder, in a way that the Jenkins attachment plugin is able to find those files and export them in the Jenkins GUI. For instance, consider the following test, when is executed in Jenkins (with the attachment plugin) and the following configuration:

mvn clean test -Dtest=DockerFirefoxWithOptionsJupiterTest -Dsel.jup.recording=true -Dsel.jup.output.folder=surefire-reports -Dsel.jup.screenshot.at.the.end.of.tests=true

In this case, at the the execution of this test, two recordings in MP4 and two screenshots in PNG will be attached to the job as follows.

jenkins attachements test
Figure 8. Example of test execution through Jenkins with attachments

We can watch the recording simply clicking in the attached MP4 files.

jenkins attachements test mp4
Figure 9. Example of test execution through Jenkins with attachements

Test template are also compatible with this feature. For instance, consider the following test test. When is executed in Jenkins using the configuration below, the following attachments will be available on Jenkins:

mvn clean test -Dtest=TemplateTest -Dsel.jup.recording=true -Dsel.jup.output.folder=surefire-reports -Dsel.jup.screenshot.at.the.end.of.tests=true
jenkins attachements template
Figure 10. Example of template test execution through Jenkins with attachments

And we will be able to watch the recording:

jenkins attachements template mp4
Figure 11. Example of template test execution through Jenkins with attachments

Configuration

Default configuration parameters for Selenium-Jupiter are set in the selenium-jupiter.properties file. The following table summarizes all the configuration keys available.

Table 3. Configuration keys in Selenium-Jupiter
Configuration key Description Default value

sel.jup.vnc

Check VNC session for Docker browsers

false

sel.jup.vnc.screen.resolution

Screen resolution of VNC sessions (format <width>x<height>x<colors-depth>)

1920x1080x24

sel.jup.vnc.create.redirect.html.page

Redirect VNC URL to HTML page

false

sel.jup.vnc.export

Java property name in which the VNC URL will be exported

vnc.session.url

sel.jup.recording

Record Docker browser session (in MP4 format)

false

sel.jup.recording.video.screen.size

Video screen size for recordings (width and height)

1024x768

sel.jup.recording.video.frame.rate

Video frame rate for recordings

12

sel.jup.recording.image

Docker image for recordings

selenoid/video-recorder:latest

sel.jup.output.folder

Output folder for recordings, screenshots, and HTML redirect pages

.

sel.jup.screenshot.at.the.end.of.tests

Make screenshots at the end of the test

false

sel.jup.screenshot.format

Format for screenshots

base64

sel.jup.exception.when.no.driver

Throw exception in case of exception or not

true

sel.jup.browser.template.json.file

Browsers scenario (JSON) path

classpath:browsers.json

sel.jup.default.browser

Browser for generic driver

chrome-in-docker

sel.jup.default.version

Version for generic driver

latest

sel.jup.default.browser.fallback

Fallback browser list for generic driver

chrome,firefox,safari,edge,phantomjs

sel.jup.default.browser.fallback.version

Fallback version list for generic driver

latest,latest,latest,latest,latest

sel.jup.browser.list.from.docker.hub

Update Docker images list from Docker Hub

true

sel.jup.browser.session.timeout.duration

Session timeout for Docker browsers (in Golang duration format)

1m0s

sel.jup.selenoid.image

Selenoid (Golang Selenium Hub) Docker iamage

aerokube/selenoid:1.6.0

sel.jup.selenoid.port

Selenoid port

4444

sel.jup.selenoid.vnc.password

VNC password for Selenoid sessions

selenoid

sel.jup.novnc.image

noVNC Docker image

psharkey/novnc:3.3-t6

sel.jup.novnc.port

noVNC Docker port

8080

sel.jup.chrome.image.format

Selenoid Docker images format for Chrome with VNC

selenoid/vnc:chrome_%s

sel.jup.chrome.first.version

First version of Docker Chrome (used when sel.jup.browser.list.from.docker.hub =false)

48.0

sel.jup.chrome.latest.version

Latest version of Docker Chrome (used when sel.jup.browser.list.from.docker.hub =false)

65.0

sel.jup.chrome.path

Path for Hub when using Chrome in Docker as browser

/

sel.jup.chrome.beta.image

Selenoid Docker image format for Chrome beta

elastestbrowsers/chrome:beta

sel.jup.chrome.beta.path

Path for Hub when using Chrome beta in Docker as browser

/wd/hub

sel.jup.chrome.unstable.image

Selenoid Docker image format for Chrome unstable

elastestbrowsers/chrome:unstable

sel.jup.chrome.unstable.path

Path for Hub when using Chrome unstable in Docker as browser

/wd/hub

sel.jup.firefox.image.format

Selenoid Docker images format for Firefox with VNC

selenoid/vnc:firefox_%s

sel.jup.firefox.first.version

First version of Docker Firefox (used when sel.jup.browser.list.from.docker.hub =false)

3.6

sel.jup.firefox.latest.version

Latest version of Docker Firefox (used when sel.jup.browser.list.from.docker.hub =false)

59.0

sel.jup.firefox.path

Path for Hub when using Firefox in Docker as browser

/wd/hub

sel.jup.firefox.beta.image

Selenoid Docker image format for Firefox beta

elastestbrowsers/firefox:beta

sel.jup.firefox.beta.path

Path for Hub when using Firefox beta in Docker as browser

/wd/hub

sel.jup.firefox.unstable.image

Selenoid Docker image format for Firefox unstable

elastestbrowsers/firefox:nightly

sel.jup.firefox.unstable.path

Path for Hub when using Firefox beta in Docker as unstable

/wd/hub

sel.jup.opera.image.format

Selenoid Docker images format for Opera with VNC

selenoid/vnc:opera_%s

sel.jup.opera.first.version

First version of Docker Opera (used when sel.jup.browser.list.from.docker.hub =false)

33.0

sel.jup.opera.latest.version

Latest version of Docker Opera (used when sel.jup.browser.list.from.docker.hub =false)

51.0

sel.jup.opera.path

Path for Hub when using Opera in Docker as browser

/

sel.jup.android.default.version

Default version of Android devices in Doker

7.1.1

sel.jup.android.image.api21.linux

Docker image for version 5.0.1 of Android devices in Linux

butomo1989/docker-android-x86-5.0.1:0.9-p5

sel.jup.android.image.api22.linux

Docker image for version 5.1.1 of Android devices in Linux

butomo1989/docker-android-x86-5.1.1:0.9-p5

sel.jup.android.image.api23.linux

Docker image for version 6.0 of Android devices in Linux

butomo1989/docker-android-x86-6.0:0.9-p5

sel.jup.android.image.api24.linux

Docker image for version 7.0 of Android devices in Linux

butomo1989/docker-android-x86-7.0:0.9-p5

sel.jup.android.image.api25.linux

Docker image for version 7.1.1 of Android devices in Linux

butomo1989/docker-android-x86-7.1.1:0.9-p5

sel.jup.android.image.api21.osxwin

Docker image for version 5.0.1 of Android devices in Mac and Windows

butomo1989/docker-android-arm-5.0.1:0.9-p5

sel.jup.android.image.api22.osxwin

Docker image for version 5.1.1 of Android devices in Mac and Windows

butomo1989/docker-android-arm-5.1.1:0.9-p5

sel.jup.android.image.api23.osxwin

Docker image for version 6.0 of Android devices in Mac and Windows

butomo1989/docker-android-arm-6.0:0.9-p5

sel.jup.android.image.api24.osxwin

Docker image for version 7.0 of Android devices in Mac and Windows

butomo1989/docker-android-arm-7.0:0.9-p5

sel.jup.android.image.api25.osxwin

Docker image for version 7.1.1 of Android devices in Mac and Windows

butomo1989/docker-android-arm-7.1.1:0.9-p5

sel.jup.android.novnc.port

Internal port of noVNC in Docker containers for Android devices

6080

sel.jup.android.appium.port

Internal port of Appium server in Docker containers for Android devices

4723

sel.jup.android.device.name

Default device name for Android in Docker

Samsung Galaxy S6

sel.jup.android.browser.name

Default browser name for Android in Docker

chrome

sel.jup.android.device.timeout.sec

Timeout (in seconds) to wait Android devices to be available

200

sel.jup.docker.server.url

URL to connect with the Docker Host

sel.jup.docker.wait.timeout.sec

Timeout (in seconds) to wait for Docker container

20

sel.jup.docker.poll.time.ms

Poll time (in ms) for asking to Docker container if alive

200

sel.jup.docker.default.socket

Default Docker socket path

/var/run/docker.sock

sel.jup.docker.hub.url

Docker Hub URL

https://hub.docker.com/

sel.jup.docker.stop.timeout.sec

Timeout in seconds to stop Docker containers at the end of tests

5

sel.jup.docker.api.version

Docker API version

1.35

sel.jup.docker.network

Docker network

bridge

sel.jup.docker.timezone

Timezone for browsers in Docker containers

Europe/Madrid

sel.jup.properties

Location of the properties files (in the project classpath)

/selenium-jupiter.properties

sel.jup.selenium.server.url

Selenium Server URL, to be used instead of @DriverUrl or for browsers in Docker

These properties can be overwritten in different ways. As of version 2.1.0 of Selenium-Jupiter, the configuration manager can be used:

SeleniumJupiter.config().setVnc(true);
SeleniumJupiter.config().setRecording(true);
SeleniumJupiter.config().useSurefireOutputFolder();
SeleniumJupiter.config().setBrowserListFromDockerHub(false);
SeleniumJupiter.config().wdm().setForceCache(true);
SeleniumJupiter.config().wdm().setOverride(true);

We can also use Java system properties, for example:

System.setProperty("sel.jup.recording", "true");
  1. or by command line, for example:

-Dsel.jup.recording=true

Moreover, the value of these properties can be overridden by means of environmental variables. The name of these variables result from putting the name in uppercase and replacing the symbol . by _. For example, the property sel.jup.recording can be overridden by the environment variable SEL_JUP_RECORDING.

Tuning WebDriverManager

As introduced before, Selenium-Jupiter internally uses WebDriverManager to manage the required binary to control localc browsers. This tool can be configured in several ways, for example to force using a given version of the binary (by default it tries to use the latest version), or force to use the cache (instead of connecting to the online repository to download the binary artifact). For further information about this configuration capabilities, please take a look to the WebDriverManager documentation.

In this section we are going to present a couple of simple examples tuning somehow WebDriverManger. The following example shows how to force a version number for a binary, concretely for Edge:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.edge.EdgeDriver;

import io.github.bonigarcia.SeleniumExtension;
import io.github.bonigarcia.SeleniumJupiter;

@ExtendWith(SeleniumExtension.class)
public class EdgeSettingVersionJupiterTest {

    @BeforeAll
    static void setup() {
        SeleniumJupiter.config().wdm().setDriverVersion("3.14393");
    }

    @Test
    void webrtcTest(EdgeDriver driver) {
        driver.get("http://www.seleniumhq.org/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

This other example shows how to force cache (i.e. binaries previously downloaded by WebDriverManager) to avoid the connection with online repository to check the latest version:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.chrome.ChromeDriver;

import io.github.bonigarcia.SeleniumExtension;
import io.github.bonigarcia.SeleniumJupiter;

@ExtendWith(SeleniumExtension.class)
public class ForceCacheJupiterTest {

    @BeforeAll
    static void setup() {
        SeleniumJupiter.config().wdm().setForceCache(true);
    }

    @Test
    public void test(ChromeDriver driver) {
        driver.get("https://bonigarcia.github.io/selenium-jupiter/");
        assertThat(driver.getTitle(),
                containsString("JUnit 5 extension for Selenium"));
    }

}

Screenshots

Selenium-Jupiter provides several built-in features for making screenshots for each of the browser sessions at the end of the test. These screenshots, can be encoded as Base64 or stored as PNG images. The following configuration keys are used to control the way and format in which screenshots are made:

  • sel.jup.screenshot.at.the.end.of.tests: This key indicates whether or not screenshots will be made at the end of every browser session. The accepted valued for this configuration key are:

    • true : Screenshots are always taken at the end of tests.

    • false : Screenshots are not taken at the end of tests.

    • whenfailure : Screenshots are only taken if the test fails.

  • sel.jup.screenshot.format: Format for the screenshot. The accepted values for this key are two:

    • base64 : Base64 screenshots are logged using the debug level of (Simple Logging Facade for Java (SLF4J). You can copy&paste the resulting Base 64 string in the URL bar of any browser and watch the screenshot.

    • png : Screenshots are stored as PNG images. The output folder for these images is configured using the configuration key sel.jup.output.folder (the default value of this property is ., i.e. the local folder).

Take into account that a big base64 string will be added to your logs if this option if configured. This feature can be especially useful for build server in the cloud (such as Travis CI), in which we don’t have access to the server file system but can track easily the test output log.

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running io.github.bonigarcia.test.basic.ChromeJupiterTest
...
2017-12-13 02:41:53 [main] DEBUG i.g.bonigarcia.SeleniumExtension - Screenshot (in Base64) at the end of session 5712cce700bb76d8f5f5d65a00e2c7bc (copy&paste this string as URL in browser to watch it)
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAykAAANaCAIAAAACvpRSAAAgAElEQVR4nOy9e3xV1Zn//1lr384t5+RyCDmBhJBwSRACBAqhXFQsYlGLZeygrWVGbalTtVU72gv9fgfnK3Yq36mdqTqOLTL1R8fyq2Wk3kCGSqEMlxKQiyQICZAAJySHnJyT5Nz2ZX3/ODGEk71DDuSCuN4vXrySnXX2WWfvtc767Od51vMQxhg4HA6Hw+FwOOkTDofTfQkdiH5wOBwOh8PhcEzh2ovD4XA4HA5n8ODai8PhcDgcDmfw4NqLw+FwOBwOZ/Dg2ovD4XA4HA5n8ODai8PhcDgcDmfw4NqLw+FwOBwOp5/5wx/+YPUnrr04HA6Hw+Fw+pOk8LKSX1x7cTgcDofD4fQb3SWXqfzi2o
...
nr2SgWY2TMdyRZMNENknWYTIo75WxuUWNCij9k178gCKO3BHLtkeQjiMYEgXy66/8f5nl+x57/98mYAvFysJUmckrGBLMv/dC0SlJSUjrXpwn3Ba6MgAlILbL+//8/5fAruDfRSl08WQBr2oDm4uNtmZOPKRm7GSgptWXBFJPrAAAAl0lEQVS4/biJEm74sxVmrvz///+U468MXDMRZc7zY5Aj1uDndWEG2gCWk+R5ARMgl5aZrga3N7XBT9vCbxrBWPjx/3/96tsQz8LL4foDPzSVlH4crofHwrFXxyDZx1xHaWN7EjwAiT/f69Gf/8G1O5SUDCBnj2EW8huvfnTLaFNSMsD043+89QUmwCz/yUhp9AFYT/DCDwBA0OZPpbBVqwAAAABJRU5ErkJggg==
Tests run: 2, Failures: 1, Errors: 0, Skipped: 1, Time elapsed: 7.219 sec <<< FAILURE! - in io.github.bonigarcia.test.basic.ChromeJupiterTest
testWithOneChrome(ChromeDriver)  Time elapsed: 6.594 sec  <<< FAILURE!

Known issues

Testing localhost

A daily use case in web development is testing an web application deployed in a local server (http://localhost:port/path). In the case of using Docker browsers to test this web application, we need to be aware that localhost inside a Docker container is not the local host anymore, but the container. To handle this issue, different approaches can be taken.

  • If your host (i.e. the local machine running the tests and hosting the web application under test) is Linux, Docker creates a bridge named docker0 by default. Both the Docker host and the Docker containers have an IP address on that bridge. We can find out the equivalent IP address to localhost using the following command (in this example, the address 172.17.0.1 will be used to replace localhost in our tests):

$ ip addr show docker0
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:b4:83:10:c8 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever
  • If your host is Mac and using Docker for Mac, we can use the special DNS name docker.for.mac.host.internal which will resolve to the internal IP address used by the host.

  • If your host is Windows and using Docker for Windows, we can use the special DNS name docker.for.win.host.internal.

Using old versions of Selenium

Selenium-Jupiter requires Selenium 3. In fact, selenium-java 3.x is incorporated as transitive dependency when using Selenium-Jupiter. Nevertheless, it might occur that an old version of Selenium (e.g. 2.x) is used in a project. In that case, Selenium-Jupiter will typically fail as follows:

org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [...] in executable [...]
	at org.junit.jupiter.engine.execution.ExecutableInvoker.resolveParameter(ExecutableInvoker.java:221)
	...
Caused by: java.lang.NoClassDefFoundError: org/openqa/selenium/MutableCapabilities
	at java.lang.Class.getDeclaredConstructors0(Native Method)
	at java.lang.Class.privateGetDeclaredConstructors(Unknown Source)
	at java.lang.Class.getConstructor0(Unknown Source)
	at java.lang.Class.getDeclaredConstructor(Unknown Source)
	at io.github.bonigarcia.SeleniumExtension.getDriverHandler(SeleniumExtension.java:228)
	at io.github.bonigarcia.SeleniumExtension.resolveParameter(SeleniumExtension.java:175)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.resolveParameter(ExecutableInvoker.java:207)

The solution is forcing the latest version of selenium-java to 3.x.

For example, this issue is likely to happen in Spring-Boot 1.x projects. When using a Spring-Boot 1.x parent, Selenium 2 artifacts versions are established by default. To solve it, we need to force at least the following Selenium artifacts:

    <properties>
        <selenium.version>3.11.0</selenium.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-api</artifactId>
            <version>${selenium.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-chrome-driver</artifactId>
            <version>${selenium.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-firefox-driver</artifactId>
            <version>${selenium.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-remote-driver</artifactId>
            <version>${selenium.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

Recordings in Windows or Mac

Docker for Windows/Mac only mounts by default the home of your user (e.g. C:\Users\your-user in Windows). If Selenium-Jupiter is executed from a different parent folder of that home, it won’t be possible to write the MP4 recording (by default it uses the current folder, i.e. .). This configuration can be changed using the default mappings of in Docker for Windows/Mac settings.

As an alternative, the value of the configuration key sel.jup.output.folder of Selenium-Jupiter can be used to some path inside the user folder (e.g. C:\User\your-user\whatever). The MP4 recording should end in there.

About

Selenium-Jupiter (Copyright © 2017-2018) is a project created by Boni Garcia (@boni_gg) licensed under Apache 2.0 License. This documentation is released under the terms of CC BY 3.0 (also available in PDF). There are several ways to get in touch with Selenium-Jupiter:

  • Questions about Selenium-Jupiter are supposed to be discussed in StackOverflow, using the tag selenium-jupiter.

  • Comments, suggestions and bug-reporting should be done using the GitHub issues.

  • If you think Selenium-Jupiter can be enhanced, consider contribute to the project by means of a pull request.