UITest with C# and Selenium Grid

selenium grid homepage

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
Selenium Hub with different nodes - UI Test with C#, Selenium and Selenium Grid
Selenium Hub with different nodes

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:

  1. a server (hub)
  2. 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.

Selenium Grid architecture - UI Test with C#, Selenium and Selenium Grid
Selenium Grid architecture

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.

Command Prompt with Docker compose
Command Prompt with Docker compose

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.

Docker runs Selenium Grid - UI Test with C#, Selenium and Selenium Grid
Docker runs Selenium Grid

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.

Tests running in Selenium Grid - UI Test with C#, Selenium and Selenium Grid
Tests running in Selenium Grid

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.

Microsoft Edge with Selenium IDE
Microsoft Edge with Selenium IDE

Now, click on the Selenium icon in your browser and you have a new window with Selenium IDE.

Selenium IDE from Microsoft Edge
Selenium IDE from Microsoft Edge

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.

Selenium IDE with the test
Selenium IDE with the test

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.

How to export a test to C#
How to export a test to C#

So that, you can choose what language you want and other options.

Selenium IDE exports test
Selenium IDE exports test

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.

Create a NUnit project with Visual Studio 2019
Create a NUnit project with Visual Studio 2019

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.

Test results in Test Explorer
Test results 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.

Run test in parallel in Test Explorer
Run test in parallel in Test Explorer

References

C# NuGet

Nuget latest release is 3.14.0. Released on 2018-08-02

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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.