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-compose
manually (if you know of a way to somehow use an image instead for this, let me know!) - Run
docker-compose
using a test-specificdocker-compose.yml
. - Run
dotnet test
usingdocker 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-tests
Example .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-compose
is installed, start the Selenium Hub. - Then run the dotnet tests:
- Notice it runs the
dotnet/sdk
image in the samesome-tests
network (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.json
configuration) - Notice it mounts
-v $PWD:/app
so that the output from--logger "junit"
will be readable by the GitLab CI task runner to capture test results. This requires theJUnitTestLogger
nuget 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