Since jumping on the Docker bandwagon I've found its utility spans beyond the repeatable deployments and consistent runtime environment benefits that come with the use of containers. There's a whole host of tooling and use cases emerging which take advantage of containerisation technology, one use case that I recently discovered after a conversation with Phil Jones via Twitter is the ability to quickly set up a Selenium Grid.

Setting up and configuring a Selenium Grid has never been an simple process, but thanks to Docker it's suddenly got a whole lot easier. In addition, you're now able to run your own Selenium Grid locally and greatly speed up your tests' execution. If that isn't enough then another benefit is because the tests execute inside of a Docker container, you'll no longer be blocked by your browser navigating the website you're testing!

Let's take a look at how this can be done.

Note: For the impatient, I've put together a working example of the following post in a GitHub repository you can clone and run.

Selenium Grid Docker Compose file

For those that haven't touched Docker Compose (or Docker for that matter), a Docker Compose file is a Yaml based configuration document (often named docker-compose.yml ) that allows you to configure your applications' Docker environment.

Without Docker Compose you'd need to manually run your individual Dockerfile files specifying their network connections and configuration parameters along the way. With Docker Compose you can configure everything in a single file and start your environment with a simple docker-compose up command.

Below is the Selenium Grid Docker Compose configuration you can copy and paste:

# docker-compose.yml version: '2' services: selenium_hub: image: selenium/hub:3.0.1-aluminum container_name: selenium_hub privileged: true ports: - 4444:4444 environment: - GRID_TIMEOUT=120000 - GRID_BROWSER_TIMEOUT=120000 networks: - selenium_grid_internal nodechrome1: image: selenium/node-chrome-debug:3.0.1-aluminum privileged: true depends_on: - selenium_hub ports: - 5900 environment: - no_proxy=localhost - TZ=Europe/London - HUB_PORT_4444_TCP_ADDR=selenium_hub - HUB_PORT_4444_TCP_PORT=4444 networks: - selenium_grid_internal nodechrome2: image: selenium/node-chrome-debug:3.0.1-aluminum privileged: true depends_on: - selenium_hub ports: - 5900 environment: - no_proxy=localhost - TZ=Europe/London - HUB_PORT_4444_TCP_ADDR=selenium_hub - HUB_PORT_4444_TCP_PORT=4444 networks: - selenium_grid_internal networks: selenium_grid_internal:

In the above Docker Compose file we've defined our Selenium Hub ( selenium_hub ) service, exposing it on port 4444 and attaching it to a custom network named selenium_grid_internal (which you'll see all of our nodes are on).

selenium_hub: image: selenium/hub:3.0.1-aluminum container_name: selenium_hub privileged: true ports: - 4444:4444 environment: - GRID_TIMEOUT=120000 - GRID_BROWSER_TIMEOUT=120000 networks: - selenium_grid_internal

All that's remaining at this point is to add our individual nodes. In this instance I've added two Chrome based nodes, named nodechrome1 and nodechrome2 :

nodechrome1: image: selenium/node-chrome-debug:3.0.1-aluminum privileged: true depends_on: - selenium_hub ports: - 5900 environment: - no_proxy=localhost - TZ=Europe/London - HUB_PORT_4444_TCP_ADDR=selenium_hub - HUB_PORT_4444_TCP_PORT=4444 networks: - selenium_grid_internal nodechrome2: image: selenium/node-chrome-debug:3.0.1-aluminum ...

Note: If you wanted to add Firefox to the mix then you can replace the image: value with the following Docker image:

nodefirefox1: image: selenium/node-firefox-debug:3.0.1-aluminum ...

Now if we run docker-compose up you'll see our Selenium Grid environment will spring into action.

To verify everything is working correctly we can navigate to http://0.0.0.0:4444 in our browser where we should be greeted with the following page:

Connecting Selenium Grid from .NET Core

At the time of writing this post the official Selenium NuGet package does not support .NET Standard, however there's a pending pull request which adds support (the pull request has been on hold for a while as the Selenium team wanted to wait for the tooling to stabilise). In the mean time the developer that added support released it as a separate NuGet package which can be downloaded here.

Alternatively just create the following .csproj file and run the dotnet restore CLI command.

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp1.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170123-02" /> <PackageReference Include="xunit" Version="2.2.0-beta5-build3474" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-beta5-build1225" /> <PackageReference Include="CoreCompat.Selenium.WebDriver" Version="3.2.0-beta003" /> </ItemGroup> </Project>

Next we'll create the following base class that will create a remote connection to our Selenium Grid:

public abstract class BaseTest { private IWebDriver _driver; public IWebDriver GetDriver() { var capability = DesiredCapabilities.Chrome(); if (_driver == null){ _driver = new RemoteWebDriver(new Uri("http://0.0.0.0:4444/wd/hub/"), capability, TimeSpan.FromSeconds(600)); } return _driver; } }

After that we'll create a very simple (and trivial) test that checks for the existence of an ID on google.co.uk.

public class UnitTest1 : BaseTest { [Fact] public void TestForId() { using (var driver = GetDriver()) { driver.Navigate().GoToUrl("http://www.google.co.uk"); var element = driver.FindElement(By.Id("lst-ib")); Assert.True(element != null); } } ... }

Now if we run our test (either via the dotnet test CLI command or from your editor or choice) we should see our Docker terminal console showing our Selenium Grid container jump into action as it starts executing the test one one of the registered Selenium Grid nodes.

At the moment we're only executing the one test so you'll only see one node running the test, but as you start to add more tests across multiple classes the Selenium Grid hub will start to distribute those tests across its cluster of nodes, dramatically increasing your test execution time.

If you'd like to give this a try then I've added all of the source code and Docker Compose file in a GitHub repository that you can clone and run.

The drawbacks

Before closing there are a few drawbacks to this method of running tests, especially if you're planning on doing it locally (instead of setting a grid up on a Virtual Machine via Docker).

Debugging is made harder

If you're planning on using Selenium Grid locally then you'll lose the visibility of what's happening in the browser as the tests are running within a Docker container. This means that in order to see the state of the web page on a failing test you'll need to switch to local execution using the Chrome / FireFox or Internet Explorer driver.

Reaching localhost from within a container

In this example we're executing the tests against an external domain (google.co.uk) that our container can resolve. However if you're planning on running tests against a local development environment then there will be some additional Docker configuration required to allow the container to access the Docker host's IP address.

Conclusion

Hopefully this post has broadened your options around Selenium based testing and demonstrated how pervasive Docker is becoming. I'm confident that the more Docker (and other container technology for that matter) matures, the more we'll see said technology being used for such use cases as we've witnessed in this post.