To achieve the parallel execution of Scenarios, we need to do several tweaks to our TestNG-based Selenium Test Automation Framework by using ThreadSafe & ThreadLocal<RemoteDriver>.
Firstly, download the Maven dependency by using below code –
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>3.0.0</version>
</dependency>
Create a TestNG Project and add a TestNG Test Class like below –
LaunchAppTests.java
package com.docker.tests;
import org.openqa.selenium.By;
import org.testng.Assert;
import org.testng.AssertJUnit;
import org.testng.annotations.Test;
import com.docker.utils.Hooks;
public class LaunchAppTests extends Hooks {
@Test
public void GoogleLaunchTest() throws InterruptedException {
System.out.println("Thread Id: " + Thread.currentThread().getId());
getDriver().navigate().to("http://www.google.com");
Assert.assertEquals(getDriver().getTitle(), "Google");
}
@Test
public void SauceLabDemoTest() throws InterruptedException {
System.out.println("Thread Id: " + Thread.currentThread().getId());
getDriver().navigate().to("https://www.saucedemo.com/index.html");
String title = getDriver().getTitle();
System.out.println("Page Title: " + title);
AssertJUnit.assertEquals(title, "Swag Labs");
}
@Test
public void BBCLaunchTest() {
System.out.println("Thread Id: " + Thread.currentThread().getId());
getDriver().navigate().to("https://www.bbc.co.uk/");
String title = getDriver().getTitle();
System.out.println("Page Title: " + title);
AssertJUnit.assertEquals(title, "BBC - Home");
}
public void SauceLabLoginTest() throws InterruptedException {
System.out.println("Thread Id: " + Thread.currentThread().getId());
getDriver().navigate().to("https://www.saucedemo.com/index.html");
getDriver().findElement(By.id("user-name")).sendKeys("standard_user");
getDriver().findElement(By.id("password")).sendKeys("secret_sauce");
getDriver().findElement(By.id("user-name")).submit();
getDriver().findElement(By.xpath("//*[@id=\"menu_button_container\"]/div/div[3]/div/button")).click();
getDriver().findElement(By.linkText("Logout")).click();
}
}
Create another new class named – Hooks.java
The technique to execute the Cucumber Scenarios lies with ThreadSafe Tests
package com.docker.utils;
import java.net.MalformedURLException;
import java.net.URL;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Parameters;
public class Hooks {
// Declare ThreadLocal Driver (ThreadLocalMap) for ThreadSafe Tests
protected static ThreadLocal<RemoteWebDriver> driver = new ThreadLocal<>();
public Capabilities capabilities;
@BeforeMethod
@Parameters(value = { "browser" })
public void setup(String browser) throws MalformedURLException {
// Set Browser to ThreadLocalMap
driver.set(new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), getCapabilities(browser)));
}
public WebDriver getDriver() {
// Get driver from ThreadLocalMap
return driver.get();
}
@AfterMethod
public void tearDown() {
getDriver().quit();
}
@AfterClass
void terminate() {
// Remove the ThreadLocalMap element
driver.remove();
}
private Capabilities getCapabilities(String browser) {
if (browser.equals("firefox"))
capabilities = getFirefoxOptions();
else
capabilities = getChromeOptions();
return capabilities;
}
private ChromeOptions getChromeOptions() {
ChromeOptions options = new ChromeOptions();
return options;
}
private FirefoxOptions getFirefoxOptions() {
FirefoxOptions options = new FirefoxOptions();
FirefoxProfile profile = new FirefoxProfile();
profile.setPreference("network.proxy.type", 0);
options.setCapability(FirefoxDriver.PROFILE, profile);
return options;
}
}
Time to create a TestNG.xml file to invoke the tests
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite thread-count="2" name="Suite" parallel="tests">
<test name="com.FirstTest" parallel="methods" thread-count="10">
<parameter name="browser" value="chrome" />
<classes>
<class name="com.docker.tests.LaunchAppTests">
</class>
</classes>
</test>
</suite>
Now, to run the tests in Parallel in an effective way, we need to install and configure Selenium Grid. To do that I use the below docker-compose.xml file
version: '3'
services:
selenium-hub:
image: selenium/hub
ports:
- "4444:4444"
environment:
GRID_MAX_SESSION: 15
GRID_BROWSER_TIMEOUT: 300
GRID_TIMEOUT: 300
chrome:
image: selenium/node-chrome
depends_on:
- selenium-hub
environment:
HUB_PORT_4444_TCP_ADDR: selenium-hub
HUB_PORT_4444_TCP_PORT: 4444
NODE_MAX_SESSION: 2
NODE_MAX_INSTANCES: 2
Run the below command to bring up the Selenium Grid and Nodes
docker-compose up

Now, it’s time to run the TestNG.xml file. Once triggered, the Scenarios are parallelised and they run in parallel in the Selenium Grid in a Headless mode with the sessions allocated automatically.

Image Credits: https://www.linuxnix.com/join-devops-whatsapp-groups/