Back to Resources


Posted October 26, 2010

JUnit 4 and Selenium – Part Three: Parallelism and OnDemand


This is the third and final part of how to do Selenium testing in parallel. Today we get into the meat of things with parallel execution, both locally and in OnDemand. JUnit 4 doesn't ship with a true parallel solution, but Harald Wellman wrote a blog post on running parameterized JUnit tests in parallel, which does just about everything we want. But instead of having a fixed thread pool side, I modified it to use a dynamically sized one. With this new class in your project, we only need to change the @RunWith line to use our new parallel, parameterized runner.


Now the execution profile is as follows: For each item in the @Parameters Collection, launch a new thread to execute the current test method in parallel. Have two browser strings? Then two threads run in parallel. Have eight browser strings? Well, that is eight threads running in parallel. This means that however long it takes run a single browser through all your scripts, that's how long it will take to run against any number of browsers. Win! Well, sure, but only if you have all those browsers on your machine or have an Se-Grid installation at your disposal. Most people do not have either luxury, so that is where Sauce OnDemand comes into the mix. Hosted in the cloud, you don't need to have the browsers on your machine or even behind the firewall. With some minor modification to our example class, we can have a script that runs locally or in the OnDemand cloud.


import java.util.Collection; import java.util.List; import java.util.LinkedList; import java.util.Arrays;

import; import java.util.Properties;

import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters;

import com.thoughtworks.selenium.DefaultSelenium; import com.thoughtworks.selenium.Selenium;

import; import; import;

import com.saucelabs.junit.Parallelized; import com.saucelabs.ondemand.ConnectionParameters;

@RunWith(Parallelized.class) public class TestParallelOnDemand { private Selenium selenium; private String browser; private String browserVersion; private String os; public static Properties browserProps = new Properties(); private Properties parallelProps = new Properties(); private String json;


So what has changed? The @Parameters are loaded differently again. When the script was only ever going to run locally, the browser string provided enough information. But since it's also going to be run in OnDemand, there needs to be some extra information for the OS and browser version. Using the same pattern as part two, the browser string information has been moved from the test code and into an external properties file.

# order is important! - OS/browser/version browsers=Windows 2003;*firefox;3.6.,Windows 2003;*googlechrome;3.,

OnDemand integrates with Selenium scripts by sending a JSON string with the configuration information to their server, which is why there is a new decision in the @Before method. An important design pattern when creating scripts that run locally or in some other environment is to add a switch in the script to determine where to go. You don't want to have to modify code itself in order to run between different environments. The JSON itself is an interesting challenge. Just as changing the source code didn't make sense when parameterizing the individual browser strings, hard coding the JSON doesn't make much sense either. To build the JSON in code, the Gson library was used. Here is the ConnectionParameters class that Gson is building the connection information from.

package com.saucelabs.ondemand;

import; import java.util.Properties;

public class ConnectionParameters { // hyphenated strings like access-key and browser-version need to be camel-case public String username; public String accessKey; public String os; private String browser; public String browserVersion; public String jobName;

// gson does not convert transient fields private transient String propertiesFile = "/"; private transient Properties ondemandProperties = new Properties();

public ConnectionParameters() throws Exception { InputStream is = this.getClass().getResourceAsStream(propertiesFile); ondemandProperties.load(is); this.username = ondemandProperties.getProperty("username"); this.accessKey = ondemandProperties.getProperty("access-key"); }

// ondemand browser strings cant start with the * public void setBrowser(String browser) { if (browser.startsWith("*")) { this.browser = browser.substring(1); } }

public String getBrowser() { return this.browser; } }

It should also be no surprise by now that the username and access key is loaded from an external file, as that can change and we wouldn't want to recompile for something like that. The only other thing of interest is the getter/setter for the browser string. Recall that the string can be used by either the local Se-RC server or OnDemand. Currently, OnDemand does not like having the * at the beginning, so we take care of that behind the scenes from the script. With this bit of infrastructure in place, we can run our scripts against any OS/Browser/Version combination that OnDemand currently supports or you have handy on your own hardware, and it will automatically scale execution to run each method in parallel. The example used here is a little extreme as the test itself is really only two lines, but in a real implementation scenario I would:

  • Create a custom formatter for Selenium IDE (or Sauce IDE) so when you export the recorded script, it has everything your particular framework requires

  • Create a class hierarchy for the scripts to move all the non @Test methods out of the class to make it a bit tidier

And so concludes the series on how to run Selenium scripts in parallel using JUnit 4. To see the full project, including the Maven pom, check out Parallel Test Examples in the Github repo.

Oct 26, 2010
Share this post
Copy Share Link
© 2023 Sauce Labs Inc., all rights reserved. SAUCE and SAUCE LABS are registered trademarks owned by Sauce Labs Inc. in the United States, EU, and may be registered in other jurisdictions.