Jenkins-Docker Selenium Hub Integration

Mustafa Karataş
8 min readJun 8, 2023

--

Selenium Grid

Selenium Grid allows the execution of WebDriver scripts on remote machines by routing commands sent by the client to remote browser instances.

  • Provide an easy way to run tests in parallel on multiple machines
  • Allow testing on different browser versions
  • Enable cross platform testing

Need of Selenium Grid

  1. What if you want to execute your test cases for different Operating Systems?
  2. How to run your test cases in the different version of the same browser?
  3. How to run your test cases in multiple browsers?
  4. Why should a scenario wait for the execution of other test cases even if it does not depend upon any test cases?

Installing Docker on Ubuntu

Update the apt package index and install packages to allow apt to use a repository over HTTPS:

sudo apt-get update
sudo apt-get install \
ca-certificates \
curl \
gnupg \
lsb-release

Add Docker’s official GPG key:

sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

Use the following command to set up the repository:

echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Install Docker Engine

  1. Update the apt package index:
sudo apt-get update

2.Install Docker Engine, containerd, and Docker Compose.

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

Docker installed.

docker desktop on ubuntu

Also we can install Docker desktop Debian package on Ubuntu machine

Install Jenkins on Ubuntu

Installation of Java

Jenkins requires Java in order to run, yet certain distributions don’t include this by default and some Java versions are incompatible with Jenkins.

There are multiple Java implementations which you can use. OpenJDK is the most popular one at the moment, we will use it in this guide.

Update the Debian apt repositories, install OpenJDK 11, and check the installation with the commands:

$ sudo apt update
$ sudo apt install openjdk-11-jre
$ java -version
openjdk version "11.0.12" 2021-07-20
OpenJDK Runtime Environment (build 11.0.12+7-post-Debian-2)
OpenJDK 64-Bit Server VM (build 11.0.12+7-post-Debian-2, mixed mode, sharing)

Debian/Ubuntu

On Debian and Debian-based distributions like Ubuntu you can install Jenkins through apt.

curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo tee \
/usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
/etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt-get update
sudo apt-get install jenkins

Start Jenkins

We can enable the Jenkins service to start at boot with the command:

sudo systemctl enable jenkins

Starting Jenkins

sudo systemctl start jenkins

Checking Jenkins status

sudo systemctl status jenkins
jenkins status

Jenkins is running on 8080 port

Initial Admin Password can be found:

sudo cat  /var/lib/jenkins/secrets/initialAdminPassword
admin password

Customize plugins:

plugin install
getting started

We should manage configurations

Dashboard → Manage Jenkins → Global Tool Configuration

Java Configuration:

We will download jdk from binary archive. Otherwise JAVA_HOME should be referred

Download URL for binary archive

https://download.java.net/java/GA/jdk11/13/GPL/openjdk-11.0.1_linux-x64_bin.tar.gz

Subdirectory of extracted archive

jdk-11.0.1

Label

openjdk-11

Git should be installed on your machine, otherwise git should be installed automatically

Maven Configuration

We will install maven automatically

Java Selenium Test Application

Creating very basic Java maven test application

Our pom.xml file:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.demo</groupId>
<artifactId>demo-test-project</artifactId>
<version>1.0-SNAPSHOT</version>
<name>demo-test-project</name>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<aspectj.version>1.9.9.1</aspectj.version>
<allure.version>2.19.0</allure.version>
</properties>

<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<version>${allure.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.3.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.23.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<testFailureIgnore>true</testFailureIgnore>
<argLine>
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
</argLine>
<systemProperties>
<property>
<name>allure.results.directory</name>
<value>${project.build.directory}/allure-results</value>
</property>
</systemProperties>
<suiteXmlFiles>
<suiteXmlFile>
TestNG.xml
</suiteXmlFile>
</suiteXmlFiles>
</configuration>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-maven</artifactId>
<version>2.11.2</version>
</plugin>
</plugins>
</build>
</project>

We will configurate our tests via TestNG.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Practice Suite">
<test name="Test Basics 1">
<parameter name="browserName" value="firefox-grid"/>
<classes>
<class name="com.demo.demotestproject.test.GoogleTest"></class>
</classes>
</test>
</suite>

Our BasePage class

public class BasePage {

public WebDriver driver;
public WebDriverWait wait;


JavascriptExecutor js;



public BasePage(WebDriver driver){
this.driver = driver;
wait = new WebDriverWait(driver, Duration.ofSeconds(20));
js = (JavascriptExecutor)driver;
}

public void waitForLocator(By locator){
wait.until(ExpectedConditions.elementToBeClickable(locator));
}

public WebElement findBy(By locator){
return driver.findElement(locator);
}


public void alertAccept(){
driver.switchTo().alert().accept();
}

public void clearCookies(){
driver.manage().deleteAllCookies();
}
public void maximizeWindow(){
driver.manage().window().maximize();
}

public void click(By locator){
waitForLocator(locator);
findBy(locator).click();
}

public void setText(By locator, String text){
waitForLocator(locator);
findBy(locator).clear();
findBy(locator).sendKeys(text);
}

public String getText(By locator){
waitForLocator(locator);
return findBy(locator).getText();
}

public boolean isAt(By locator){
return findBy(locator).isDisplayed();
}

}

We have implemented basic Selenium driver functions

Basic page object

public class GooglePage extends BasePage{
private final String GOOGLE_URL = "https://www.google.com/";

private By searchInputLocator = By.cssSelector("input[title='Ara']");

public GooglePage(WebDriver driver) {
super(driver);
}

public void getGooglePage(){
driver.get(GOOGLE_URL);
}

public boolean googlePageOpened(){
return isAt(searchInputLocator);
}

}

We should implement DriverManager and DriverOptions classes. When we send requests, selenium grid will understand our driver via driver options.

DriverOptions class:

public class DriverOptions {
private ChromeOptions chromeOptions;
private FirefoxOptions firefoxOptions;
private FirefoxProfile firefoxProfile;

public void setFirefoxOptions(){
firefoxOptions = new FirefoxOptions();
firefoxProfile = new FirefoxProfile();
//Accept Untrusted Certificates
firefoxProfile.setAcceptUntrustedCertificates(true);
firefoxProfile.setAssumeUntrustedCertificateIssuer(false);
//Use No Proxy Settings
firefoxProfile.setPreference("network.proxy.type", 0);
//Set Firefox profile to capabilities

//options.setCapability(FirefoxDriver.PROFILE, profile);
}

public FirefoxOptions getFirefoxOptions(){
setFirefoxOptions();
return firefoxOptions;
}



public void setChromeOptions(){
chromeOptions = new ChromeOptions();
chromeOptions.addArguments("start-maximized");
chromeOptions.addArguments("ignore-certificate-errors");
chromeOptions.addArguments("--disable-dev-shm-usage");
chromeOptions.addArguments("--no-sandbox");
chromeOptions.addArguments("--disable-popup-blocking");
}

public ChromeOptions getChrome(){
setChromeOptions();
return chromeOptions;
}

}

DriverManager class

public class DriverManager {
public WebDriver driver;
public DriverOptions driverOptions;

public void setDriver(String browserName) throws MalformedURLException {
driverOptions = new DriverOptions();

if (browserName != null){

switch (browserName){
case "chrome-local":
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
break;

case "firefox-local":
WebDriverManager.firefoxdriver().setup();
driver = new FirefoxDriver();
break;

case "chrome-grid":
driver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), driverOptions.getChrome());
break;

case "firefox-grid":
driver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), driverOptions.getFirefoxOptions());
break;

}

}

}


public void tearDown(){
if(driver != null ){
driver.quit();
}
}

}

We have used also WebDriverManager for local test runs. WebDriverManager will handle drivers via installing before test runs. This functions can be used for rapid test runs in your local machine.

If we want to use Grid we can just change parameters in TestNG.xml file, when no parameters found, test will be started automatically in our local machine.

BaseTest class


public class BaseTest extends DriverManager {


@BeforeTest
@Parameters(value = {"browserName"} )
public void initializeBrowser(@Optional String browserName) throws MalformedURLException {

if(browserName == null){
setDriver("chrome-local");

System.out.println("Local Chrome driver is started");
driver.manage().window().maximize();

}else{
setDriver(browserName);
System.out.println("Browser is opened: "+ browserName);
}
}


@AfterTest
public void terminateBrowser(){
tearDown();
System.out.println("Driver is removed");
}


}

Basic scenario test class

public class GoogleTest extends BaseTest {
GooglePage googlePage;

@Test
public void googleTest() throws InterruptedException {
googlePage = new GooglePage(driver);
googlePage.getGooglePage();
Assert.assertEquals(googlePage.googlePageOpened(), true, "Google is not opened");
}

}

We will use Selenium Hub on docker

docker-compose.yml file can be found in selenium docker github page

Docker selenium hub official image

We should add docker-compose.yml file in our source code

# To execute this docker-compose yml file use `docker-compose -f docker-compose-v3.yml up`
# Add the `-d` flag at the end for detached execution
# To stop the execution, hit Ctrl+C, and then `docker-compose -f docker-compose-v3.yml down`
version: "3"
services:
chrome:
image: selenium/node-chrome:4.1.3-20220405
shm_size: 2gb
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443

edge:
image: selenium/node-edge:4.1.3-20220405
shm_size: 2gb
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443

firefox:
image: selenium/node-firefox:4.1.3-20220405
shm_size: 2gb
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443

selenium-hub:
image: selenium/hub:4.1.3-20220405
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"

We will start container

sudo docker-compose up -d
docker selenium hub started

in 4444 port

selenium grid docker ui

Now Jenkins and Selenium Hub are ready to test. We will create a pipeline. Our tests in pipeline will trigger selenium grid on docker. Source codes will be installed from Github. Tests will be ran on selenium docker image.

If we want to scale up the browser services and we can change the parameters in docker-compose file.

Create a New Item in Jenkins

new item
pipeline

Pipeline:

pipeline {
agent any

tools {
// Install the Maven version configured as "M3" and add it to the path.
maven "M3"
}

stages {
stage('Build') {
steps {
// Get some code from a GitHub repository
git 'https://github.com/musticode/demo-test-project.git'

sh "mvn clean test"


}

}

stage('Reports') {
steps {
script {
allure([
includeProperties: false,
jdk: '',
properties: [],
reportBuildPolicy: 'ALWAYS',
results: [[path: 'target/allure-results']]
])
}
}
}

}
}

Build now

Firefox node:

Session

sessions tab

Live VNC

Password: secret

test live

Build history

builds

Build console output

build console output

Allure reports

allure reports

--

--

No responses yet