Smooth Sailing: Dockerizing automation tests for Cross-Browser scenarios
In today’s fast-paced development landscape, ensuring the compatibility of web applications across multiple browsers bight be crucial. However, managing these tests can become challenging as the number of target browsers and browser version grows. That’s where Docker comes to the rescue! By containerizing your Selenium tests, you can achieve a streamlined and efficient testing process, ensuring your web application sails without hitting rocky waters or icebergs. In this article, we’ll explore the benefits, easy step-by-step implementation, of using Docker to empower your Selenium test suite for a seamless cross-browser testing experience. At the end, we will check back-end performance metrics as well. So, hoist the Docker flag and let’s navigate the waters of test automation with confidence!
Sandbox environment
For the sandbox environment, once again, I’m going to use Restful Booker Platform and its send message feature.
Restful Booker is an API playground created by Mark Winteringham for those wanting to learn more about API testing and tools.
The automation testing code is simple. I did not implement any design pattern, best practices, helpers and similar but focus on running the cross browser scenarios easy way.
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using SeleniumExtras.WaitHelpers;
namespace Dockerized
{
public class Tests
{
ChromeDriver driver;
[SetUp]
public void Setup()
{
ChromeOptions options = new();
options.AddArgument("--disable-notifications");
options.AddArgument("--start-maximized");
driver = new ChromeDriver(options);
driver.Navigate().GoToUrl("https://automationintesting.online/");
}
[Test]
public void Test1()
{
driver.FindElement(By.Id("name"))
.SendKeys("Automation Tester");
driver.FindElement(By.Id("email"))
.SendKeys("Automation@Tester.Com");
driver.FindElement(By.Id("phone"))
.SendKeys("123456789012345");
driver.FindElement(By.Id("subject"))
.SendKeys("Hello World!");
driver.FindElement(By.Id("description"))
.SendKeys("Hello, this is an automated message!");
driver.FindElement(By.Id("submitContact"))
.Click();
new WebDriverWait(driver, TimeSpan.FromSeconds(10))
.Until(ExpectedConditions.ElementIsVisible(By.XPath("//p[text()=\"We'll get back to you about\"]")));
}
[TearDown]
public void Teardown()
{
driver.Dispose();
}
}
}
The plan
In order to run stable and parallel cross browser test automation scenarios we will be using Docker Desktop and Selenium Grid! But first, let’s understand what Docker Desktop is and how it can conduct testing. Docker Desktop is a powerful tool that enables developers to create, deploy, and manage applications using containers. Containers are lightweight and portable environments that encapsulate all the necessary components, including code, runtime, libraries, and dependencies, needed to run an application. With Docker, we can avoid the hassle of dealing with environment discrepancies and ensure consistent performance across various systems.
In the world of Docker, we encounter two essential terms: Docker images and containers.
- Docker images serve as the blueprints or templates for containers. They are read-only snapshots that contain everything required to run your application. When you start a container from an image, it becomes a live instance of that image.
- Containers are isolated, meaning they run independently of the host system, ensuring that applications operate consistently across different environments.
Now, let’s introduce Selenium Grid — an invaluable tool for speeding up test execution and increasing test coverage. Selenium Grid allows you to distribute test execution across multiple browsers and operating systems, enabling parallel testing and reducing test suite execution time significantly. It acts as a centralized hub that connects multiple Selenium nodes running different browser configurations, making cross-browser testing a breeze.
Let’s try it out!
Putting the plan into action
i) Download and install Docker Desktop
After you download and install the Docker Desktop, make sure it is up and running by enter the command below to CMD.
docker --version
ii) pull necessary selenium grid docker images
Create a new file “docker-compose.yml” at the root of your project with the following content:
version: "3"
services:
chrome:
image: selenium/node-chrome:4.10.0-20230607
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
- SE_NODE_MAX_SESSIONS=1
edge:
image: selenium/node-edge:4.10.0-20230607
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
- SE_NODE_MAX_SESSIONS=1
firefox:
image: selenium/node-firefox:4.10.0-20230607
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
- SE_NODE_MAX_SESSIONS=1
selenium-hub:
image: selenium/hub:4.10.0-20230607
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
Our Docker Compose YAML file includes the following components:
- Version: Specifies the version of the Compose file format. In our case that’s “3”.
- Services: Defines the different containers (services) that make up our grid. Each service can have an associated image, environment variables, volumes, ports, and other configuration options.
- Dependencies: Specifies the dependencies between services, ensuring that certain services start or wait for others to be ready before launching. Over here we are waiting for the hub to be up and running prior to creating nodes.
iii) Navigate CMD to the location of the docker compose file and run the command:
docker-compose up - d
…wait for process do complete.
Open your web browser and navigate to http://localhost:4444/ and viola! We set up a local grid environment just like that.
Note: As needed, we can shut down the grid with a simple command:
docker-compose down
iv) modify and run the code on the grid
Update the existing code as follows:
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Remote;
using OpenQA.Selenium.Support.UI;
using SeleniumExtras.WaitHelpers;
namespace Dockers
{
[Parallelizable(ParallelScope.All)]
[TestFixture("chrome")]
[TestFixture("edge")]
[TestFixture("firefox")]
public class ParallelTests
{
private RemoteWebDriver driver;
private string browserName;
public ParallelTests(string browser)
{
browserName = browser;
}
[SetUp]
public void Setup()
{
dynamic options = GetRemoteBrowserOptions();
options.AddArgument("--headless");
options.AddArgument("--disable-gpu");
string hubUrl = "http://localhost:4444";
driver = new RemoteWebDriver(new Uri(hubUrl), options.ToCapabilities());
driver.Navigate().GoToUrl("https://automationintesting.online/");
}
[Test]
public void Test1()
{
driver.FindElement(By.Id("name"))
.SendKeys("Automation Tester");
driver.FindElement(By.Id("email"))
.SendKeys("Automation@Tester.Com");
driver.FindElement(By.Id("phone"))
.SendKeys("123456789012345");
driver.FindElement(By.Id("subject"))
.SendKeys("Hello World!");
driver.FindElement(By.Id("description"))
.SendKeys("Hello, this is an automated message!");
driver.FindElement(By.Id("submitContact"))
.Click();
new WebDriverWait(driver, TimeSpan.FromSeconds(10))
.Until(ExpectedConditions.ElementIsVisible(By.XPath("//p[text()=\"We'll get back to you about\"]")));
}
[TearDown]
public void Teardown()
{
driver.Dispose();
}
private dynamic GetRemoteBrowserOptions()
{
switch (browserName.ToLower())
{
case "chrome":
return new ChromeOptions();
case "edge":
return new EdgeOptions();
case "firefox":
return new FirefoxOptions();
default:
ChromeOptions options = new();
return options;
}
}
}
}
What we did over here:
- updated the driver class to RemoteWebDriver so we can run it on a remote agent.
- Added test class attributes Parallelizable & TestFixture to run the test across different browsers.
- Updated driver instance to point to our local grid -> http://localhost:4444
- Implemented a new method GetRemoteBrowserOptions() to create an appropriate driver.
Now, when everything is set, we can build the project, run the test and monitor it’s execution an the grid.
Wooohoooo! We’ve successfully implemented Selenium Grid using Docker Compose, and our cross-browser testing setup is running in parallel. But why stop there when you can spice things up even further?
Let’s take it to the next level by creating a new container dedicated to running your test suite. By doing so, you can achieve a clean and isolated testing environment with all the necessary dependencies pre-configured.
Build the “selenium-tests” container
Push the code to the github and create a Dockerfile at the root of the project:
FROM mcr.microsoft.com/dotnet/sdk:6.0
# Install Git
RUN apt-get update
RUN apt-get install -y git
WORKDIR /app
# Clone your test project repository
RUN git clone https://github.com/eneskuehn/Dockerized.git
WORKDIR /app/Dockerized
RUN dotnet build
RUN apt-get update
CMD ["dotnet", "test"]
What we did over here? Let’s break down each part of the Dockerfile:
- FROM mcr.microsoft.com/dotnet/sdk:6.0 line specifies the base image to use for the new image. In this case, it's using the official .NET Core SDK version 6.0 image provided by Microsoft. The image contains the necessary tools to build and run .NET applications.
- RUN apt-get update
RUN apt-get install -y git
lines update the package list and then install Git inside the Docker image. Installing Git allows the image to clone repositories from Git repositories. - WORKDIR /app sets the working directory inside the image to /app, creating the directory if it doesn't exist. All our other commands will be executed in this directory.
- RUN git clone https://github.com/eneskuehn/Dockerized.git command clones our code into the /app directory inside the image.
- RUN dotnet build builds the .NET project found in the /app/Dockerized directory. It compiles the source code and generates the necessary binaries.
- RUN apt-get update updates the package list again. It's not entirely necessary at this point, but it's included to ensure the package list is up to date in case any other package installations are added later.
- CMD [“dotnet”, “test”] executes the tests .
The next step is to include the image to docker-compose.yml file (update the file):
version: "3"
services:
chrome:
image: selenium/node-chrome:4.10.0-20230607
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
- SE_NODE_MAX_SESSIONS=1
edge:
image: selenium/node-edge:4.10.0-20230607
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
- SE_NODE_MAX_SESSIONS=1
firefox:
image: selenium/node-firefox:4.10.0-20230607
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
- SE_NODE_MAX_SESSIONS=1
selenium-hub:
image: selenium/hub:4.10.0-20230607
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
selenium-tests:
build:
context: .
dockerfile: Dockerfile
depends_on:
- selenium-hub
links:
- selenium-hub
Update the grid url to match the grid hub machine name, push the changes to git and run the docker-compose up -d command.
Docker Compose helped us in a clever way to unleash the full potential of parallel testing with Selenium Grid, enabling seamless execution across multiple browsers and operating systems as needed. Moreover, with our test runner container (selenium-tests), we’ve achieved an isolated testing environment.
Why don’t we monitor app performance?
The only reason why we don’t monitor the app performance is because we didn’t create a script yet ;)
Application performance can be a critical factor in the success of our testing efforts. Performance degradation can lead to false negative results and hinder the accuracy of our tests. To ensure a comprehensive testing approach, we must address this challenge head-on.
To track performance metrics effectively, will incorporate performance testing into our Docker-based testing environment. Let’s create a new container to handle performance measurement, and equip it with the powerful toolset of k6.
Grafana k6 is an open-source load testing tool that makes performance testing easy and productive for engineering teams. k6 is free, developer-centric, and extensible.
Create a new folder — file stricture as displayed below:
Define the newly created Dockerfile as:
# Stage 1: Build and run the code
FROM grafana/k6:latest
USER root
RUN apk add --update git
WORKDIR /app
# Clone your test project repository
RUN git clone https://github.com/eneskuehn/Dockerized.git
WORKDIR /app/Dokcerized/Performance
CMD ["run", "SmokeTest.js"]
…and SmokeTest.js as:
import http from 'k6/http';
import { sleep, check } from 'k6';
export const options = {
"iterations": 100,
"vus": 100,
thresholds: {
http_req_failed: ['rate < 0.1'], //request failure rate < 10%
},
};
const headerParams = {
headers: { "Content-Type": "application/json" },
};
const data =
{
name: "Load Tested",
email: "Loadinho@email.com",
phone: "01402 619211",
subject: "Booking enquiry",
description: "I would like to book a room at your place"
};
export default function () {
const resPost = http.post("https://automationintesting.online/message/", JSON.stringify(data), headerParams);
check(resPost, {
"is status 201": (r) => r.status === 201,
});
sleep(1);
const resGet = http.get("https://automationintesting.online/message/", headerParams);
check(resGet, {
"is status 200": (r) => r.status === 200,
"is number of messages > 0": (r) => r.json(["messages"]).length > 0,
});
sleep(1);
}
With those two files, we built a definition for a new image that is capable of running k6 script — SmokeTest.js that will send 100 concurrent messages and verify that user can read it all.
Update docker-compose.yml and add a new service k6-tests at the end of the file.
.
.
.
k6-tests:
build: ./Performance/Docker
depends_on:
- selenium-hub
links:
- selenium-hub
Now, save everything, push to git and execute docker-compose up -d.
Conclusion
In conclusion, our journey into open sea of dockerized parallel testing and performance metrics monitoring has opened a whole new bunch of possibilities through planning and implementation of new test levels. If you ask me, it is super cool to have everything set up in a set-it-and-forget-it way with option to run a large scale testing with a single command.
In case you want to try it out, find the code here.
Until the next time, happy testing.