I explain how I have created UITest with C#, Selenium and Selenium Grid for the AdminLTE integration and other projects.
So, speeding up the test execution process is on every test engineer’s mind when working with a custom Selenium framework. Even more so if you’re working on a long-term project, and if you are dealing with a tight regression period.
Therefore, when in need to perform testing on different browsers, versions, and operating systems, the good news is that the solution is already out there.
Then, we just need to set it up and start using it. The source code of this project is on GitHub.
What Is Selenium Grid?
Selenium Grid is a testing tool that allows us to run tests on different machines against different browsers.
So, it is part of the Selenium Suite, which specializes in running multiple tests across different browsers, operating systems, and machines. You can connect to it with Selenium Remote by specifying:
- the browser
- browser version
- operating system
For example: A hub can be connected to three different nodes, each running a separate browser or different browser version. When tests are run, request is sent to hub which searches for node with specified criteria. Once hub manages to locate it, scripts are sent to the node and tests are run.
Architecture overview
First, we have to understand the architecture for creating UITest with C#, Selenium and Selenium Grid.
You can use RemoteWebDriver
the same way you would use WebDriver locally. The primary difference is that RemoteWebDriver
needs to be configured so it can run your tests on a separate machine.
The RemoteWebDriver
is composed of two pieces: a client and a server. The client is your WebDriver test and the server is simply a Java servlet.
The RemoteWebDriver
is an implementation class of the WebDriver interface. So, a developer can use to execute their test scripts via the RemoteWebDriver
server on a remote machine.
There are two parts to RemoteWebDriver
:
- a server (hub)
- a client (node)
The RemoteWebDriver
server is a component that listens on a port for various requests from a RemoteWebDriver
. Once it receives the requests, it forwards them to any of the browser driver, whichever is called for.
The language-binding client libraries that serve as a RemoteWebDriver
client, as it used to when executing tests locally, translate your test script requests to JSON payload and sends them across to the RemoteWebDriverserver
using the JSON wire protocol.
When you execute your tests locally, the WebDriver client libraries talk to your browser driver directly.
Now, when you try to execute your tests remotely, the WebDriver client libraries talk to the RemoteWebDriverserver. The server talks to either the Firefox Driver, IE Driver, or Chrome Driver, depending which one of these the WebDriver client asks for.
But Why Docker?
So, firing up Selenium Grid architecture can be quite a long process. Selenium JAR will have to be downloaded in hub and each of the node. After this you have to fire the command in the hub to get the server up. From that, you can get the IP address. In the nodes, you can get the server up by adding the IP of the hub and port number. This is very time consuming and tedious process.
Things become more difficult to manage when tests are required to be run on different versions of a browser. Managing multiple browser versions on a system and running tests on them is an exhausting process.
Docker manages these tasks with relative ease.
Run Docker compose
For what I said above, I’m looking for a simple way to install everything quickly. So, I can run my test against different environments.
First, we have to understand a bit of Docker: you can read this post to have good start and install Docker in your machine. Then, we can use docker-compose
to download, install and run everything we need.
So, in your machine, create a folder for UITest
. Then, create a file called docker-compose.yml
(be careful the name is impotant). The content of this file is the following:
version: "3"
services:
chrome:
image: selenium/node-chrome:4.0.0-beta-1-20210215
volumes:
- /dev/shm:/dev/shm
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- NODE_MAX_CONCURRENT_SESSIONS=5
ports:
- "6900:5900"
edge:
image: selenium/node-edge:4.0.0-beta-1-20210215
volumes:
- /dev/shm:/dev/shm
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- NODE_MAX_CONCURRENT_SESSIONS=5
ports:
- "6901:5900"
firefox:
image: selenium/node-firefox:4.0.0-beta-1-20210215
volumes:
- /dev/shm:/dev/shm
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- NODE_MAX_CONCURRENT_SESSIONS=5
ports:
- "6902:5900"
opera:
image: selenium/node-opera:4.0.0-beta-1-20210215
volumes:
- /dev/shm:/dev/shm
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- NODE_MAX_CONCURRENT_SESSIONS=5
ports:
- "6903:5900"
selenium-hub:
image: selenium/hub:4.0.0-beta-1-20210215
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
The file is combining all the images for Chrome, Edge, Firefox, Opera and Selenium Grid itself. To have more instances for each browser, I add a setting in the environment
that allow us to have 5 instances.
- NODE_MAX_CONCURRENT_SESSIONS=5
So, that will give us the opportunity to run multiple tests in parallel.
Now, click on the address bar of the file explorer and type CMD
, then Enter
. You have opened the Command Prompt in the UITest
folder. Then, run the following command:
docker-compose up
So, you can see that Docker starts to download and install all images for the Selenium Grid.
When the installation is completed, open Docker and you can see something similar to the following image: all images for Selenium Grid are up and running.
Finally, I want to open the Selenium Grid website to see if everything is ready to go. Open your browser and type this URL
https://localhost:4444/
At this point, you have a browse with something similar to the following image: Selenium Grid with the 4 browsers each one with 5 instances.
First part of the job is done!
Selenium IDE
So far, I shown how to install Selenium as a server for UITest with C# and Selenium Grid. With C# we can write the test but it could be a little bit boring and difficult. Here, enter on the scene Selenium IDE.
It is working with Chrome, Edge (some as Chrome) and Firefox. Click on one of the links and install the extension for your browser.
Now, click on the Selenium icon in your browser and you have a new window with Selenium IDE.
So, you can select one of the options from the list. Because it is the first time, I want to Record a new test in a new project. Then, insert a project name and then the URL. Then, start recording. A new browser will be opened and click around. When you are done, go to the Selenium IDE window and click on Stop.
Here, you can see everything you click or scroll or type in any input box. Quite cool, eh? If you save this test, you can re-open it later and run again the test. If you want to run, you can follow step-by-step the procedure, change the steps and see the results.
Now, we can export all tests in a C# file (or other language). Move the mouse on the test you want to export, click on the 3 dots and from the menu select Export.
So that, you can choose what language you want and other options.
Finally, we have a C# file with the test. How to use it in a .NET project?
Create NUnit project
Now, open Visual Studio and create a new NUnit project.
First, I want to implement a factory class to create a new instance of the IWebDriver
for each browser. Then, I’m going to create an enum
for the BrowserType
.
using System;
using System.Collections.Generic;
using System.Text;
namespace NUnitTestProject1.Enums
{
public enum BrowserType
{
Chrome,
Edge,
Firefox,
Opera
}
}
Now, the driver factory has to consider if I want to run locally or on the Selenium Grid the tests. For that, I’m going to create 2 functions: CreateInstance
with the browser type as parameter for local run and CreateInstance
with the browser type and the Selenium Grid.
using Microsoft.Edge.SeleniumTools;
using NUnitTestProject1.Enums;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Opera;
using OpenQA.Selenium.Remote;
using System;
using System.Collections.Generic;
using System.Text;
namespace NUnitTestProject1.Factories
{
public static class LocalDriverFactory
{
public static IWebDriver CreateInstance(BrowserType browserType)
{
IWebDriver driver = null;
switch (browserType)
{
case BrowserType.Chrome:
driver = new ChromeDriver();
break;
case BrowserType.Edge:
var options = new EdgeOptions();
driver = new EdgeDriver(options);
break;
case BrowserType.Firefox:
driver = new FirefoxDriver();
break;
case BrowserType.Opera:
break;
}
return driver;
}
public static IWebDriver CreateInstance(BrowserType browserType, string hubUrl)
{
IWebDriver driver = null;
TimeSpan timeSpan = new TimeSpan(0, 3, 0);
switch (browserType)
{
case BrowserType.Chrome:
ChromeOptions chromeOptions = new ChromeOptions();
driver = GetWebDriver(hubUrl, chromeOptions.ToCapabilities());
break;
case BrowserType.Edge:
EdgeOptions options = new EdgeOptions();
driver = GetWebDriver(hubUrl, options.ToCapabilities());
break;
case BrowserType.Firefox:
FirefoxOptions firefoxOptions = new FirefoxOptions();
driver = GetWebDriver(hubUrl, firefoxOptions.ToCapabilities());
break;
case BrowserType.Opera:
OperaOptions operaOptions = new OperaOptions();
driver = GetWebDriver(hubUrl, operaOptions.ToCapabilities());
break;
}
return driver;
}
private static IWebDriver GetWebDriver(string hubUrl, ICapabilities capabilities)
{
TimeSpan timeSpan = new TimeSpan(0, 3, 0);
return new RemoteWebDriver(
new Uri(hubUrl),
capabilities,
timeSpan
);
}
}
}
Now, we can copy the test generated by Selenium IDE in a new test class. In the SetUp
function I’m going to add the hubUrl
where I save this address:
https://localhost:4444/wd/hub
In the following example, I added a couple of other simple tests just to give you more references.
using NUnit.Framework;
using NUnitTestProject1.Factories;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.IE;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Remote;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace NUnitTestProject1
{
public class Tests
{
private IWebDriver driver;
string hubUrl;
public IDictionary<string, object> vars { get; private set; }
private IJavaScriptExecutor js;
[SetUp]
public void Setup()
{
vars = new Dictionary<string, object>();
hubUrl = "https://localhost:4444/wd/hub";
driver = LocalDriverFactory.CreateInstance(Enums.BrowserType.Edge, hubUrl);
js = (IJavaScriptExecutor)driver;
}
[TearDown]
protected void TearDown()
{
driver.Quit();
}
[Test]
[Parallelizable]
public void OpenGoogleAndSearch()
{
driver.Navigate().GoToUrl("https://www.google.com");
driver.Manage().Window.Maximize();
driver.FindElement(By.Name("q")).SendKeys("I Want to se this on a remote machine");
}
[Test]
[Parallelizable]
public void OpenBingAndSearch()
{
driver.Navigate().GoToUrl("https://www.bing.com/");
driver.Manage().Window.Maximize();
driver.FindElement(By.Name("q")).SendKeys("I Want to seee this on a remote machine");
}
[Test]
[Parallelizable]
public void SearchOnPureSourceCode()
{
driver.Navigate().GoToUrl("https://puresourcecode.com/");
driver.Manage().Window.Maximize();
driver.FindElement(By.CssSelector("#simplemodal-container a.modalCloseImg")).Click();
driver.FindElement(By.CssSelector("#search-2 > #searchform .form-control")).Click();
driver.FindElement(By.CssSelector("#search-2 > #searchform .form-control")).SendKeys("adminlte");
driver.FindElement(By.CssSelector("#search-2 > #searchform .btn")).Click();
js.ExecuteScript("window.scrollTo(0,1929)");
js.ExecuteScript("window.scrollTo(0,2463)");
js.ExecuteScript("window.scrollTo(0,1198)");
js.ExecuteScript("window.scrollTo(0,1037)");
js.ExecuteScript("window.scrollTo(0,437)");
driver.FindElement(By.CssSelector(".d-md-flex:nth-child(1) .title > a")).Click();
var element = driver.FindElement(By.CssSelector(".homebtn"));
Actions builder = new Actions(driver);
builder.MoveToElement(element).Perform();
}
}
}
Finally, we can run the test. All the test are running again the Selenium Grid in my code. The result is as usual the result of tests in Test Explorer.
The attribute [Parallelizable]
allows Visual Studio to run your tests in parallel. Also, you can check if Visual Studio is running the test in parallel when you click on the gear in the Test Explorer.
References
- Selenium (website)
- Downloads
- Getting started: Documentation for Selenium
- SeleniumHQ/docker-selenium: Docker images for the Selenium Grid Server
- Source code of this example on GitHub
C# NuGet
Nuget latest release is 3.14.0. Released on 2018-08-02
- WebDriver
- WebDriverBackedSelenium
- Support
- RC (Final version 3.1.0 Released 2017-02-16)
Selenium IDE
Selenium IDE is a Chrome and Firefox plugin which records and plays back user interactions with the browser. Use this to either create simple scripts or assist in exploratory testing.
Download latest released version for Chrome or for Firefox or view the Release Notes.
Download previous IDE versions here.