Jenkins-Docker Selenium Hub Integration
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
- What if you want to execute your test cases for different Operating Systems?
- How to run your test cases in the different version of the same browser?
- How to run your test cases in multiple browsers?
- 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
- 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.
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 is running on 8080 port
Initial Admin Password can be found:
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
Customize plugins:
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
in 4444 port
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
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
Live VNC
Password: secret
Build history
Build console output
Allure reports