To run dotnet test in GitLab and have it connect to and use containerized services like databases, caches, or in this case Selenium Hub, you can use the following pattern:
- Rely on GitLab CI’s built-in
docker:dind(“docker in docker”) service to run docker commands as job scripts. - Install
docker-composemanually (if you know of a way to somehow use an image instead for this, let me know!) - Run
docker-composeusing a test-specificdocker-compose.yml. - Run
dotnet testusingdocker run [...] mcr.microsoft.com/dotnet/sdk:5.0 dotnet test [...].
Example xUnit Test Class
This is using xUnit, pulling the Selenium Hub URL from appsettings.json, and grabbing the title of google.com.
SomeBrowserTests.cs
public class SomeBrowserTests
{
[Fact]
public void GetTitleTest()
{
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env}.json", optional: true)
.Build();
var webDriverHubUrl = configuration.GetSection("Selenium")["HubUrl"];
var options = new ChromeOptions();
var hubUri = new Uri(webDriverHubUrl);
using var driver = new RemoteWebDriver(hubUri, options);
driver.Url = "https://www.google.com";
var title = driver.Title;
Assert.Contains("Google", title);
driver.Quit();
}
}Example appsettings.json
This is the appsettings.json that GitLab CI will use. For local development it’s possible to use a separate appsettings.json pointed to localhost, since a local docker-compose setup for development would expose the necessary ports on localhost. However, GitLab CI creates a new container for each job script, so anything running there will have to connect from the a test container to the Selenium Hub container using the container name that docker provides via it’s magic DNS stuff. (this took me a long time to figure out, and adding a job script of docker network ls helped.)
appsettings.CI.json
{
"Selenium": {
"HubUrl": "http://some-selenium-hub:4444/wd/hub"
}
}Example docker-compose.yml
Nothing fancy here (this is close to what the Selenium docs show), other than to note what network name (bridged by default) is used (some-tests) because it will be used later when running dotnet test.
docker-compose.some-tests.yml
version: "3.3"
networks:
some-tests:
services:
some-selenium-chrome:
image: selenium/node-chrome:4.0
restart: always
volumes:
- /dev/shm:/dev/shm
environment:
- SE_EVENT_BUS_HOST=some-selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
entrypoint: bash -c 'SE_OPTS="--host $$HOSTNAME" /opt/bin/entry_point.sh'
networks:
- some-tests
depends_on:
- some-selenium-hub
some-selenium-hub:
image: selenium/hub:4.0
restart: always
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
networks:
- some-testsExample .gitlab-ci.yml
Here’s where the rubber meets the road.
- Alpine now allows
apk add docker-compose(so there’s no need to mess with python/pip installation ofdocker-compose🙌). - After
docker-composeis installed, start the Selenium Hub. - Then run the dotnet tests:
- Notice it runs the
dotnet/sdkimage in the samesome-testsnetwork (so that it can connect tohttp://some-selenium-hub:4444/wd/hub) - Notice it sets the
ASPNETCORE_ENVIRONMENT=CI(so that the test will read from theappsettings.CI.jsonconfiguration) - Notice it mounts
-v $PWD:/appso that the output from--logger "junit"will be readable by the GitLab CI task runner to capture test results. This requires theJUnitTestLoggernuget package to be installed in the test project(s). - Notice it calls
docker-compose down, and I’m not entirely sure that’s necessary – I just like to be a good citizen.
- Notice it runs the
.gitlab-ci.yml
image: docker:latest
services:
- name: docker:dind
entrypoint: ["dockerd-entrypoint.sh"]
command: ["--max-concurrent-downloads", "6"]
stages:
- test
variables:
DOCKER_DRIVER: overlay2
some-tests:
stage: test
before_script:
- apk add docker-compose
script:
- docker-compose -f docker-compose.some-tests.yml up -d --build --remove-orphans
- docker run --network some-tests -v $PWD:/app -w /app -e "ASPNETCORE_ENVIRONMENT=CI" mcr.microsoft.com/dotnet/sdk:5.0-alpine dotnet test SomeSolution.sln --logger "junit"
- docker-compose -f docker-compose.some-tests.yml down
artifacts:
reports:
junit: ./**/TestResults/TestResults.xml