Background

This will be a slightly shorter article. In this article I will show you how I’ve managed to do some end-to-end testing with Gitlab CI services.

I’m building a browser-based multiplayer game called Banter Bus. Banter Bus consists of three main components,

  • gui: A SvelteKit based frontend the user will interact with to play the game
  • core-api: A Socketio API written in Python
  • management-api: A simple RESTful API written in Python (FastAPI)

Now say I want to write some e2e Cypress tests, that will test all of these components interacting with each other. Which mainly will look something like gui -> core-api -> management-api.

Each of these project deploys its own Docker container, which we can then use for testing it. So how can we do this with Gitlab CI ?

Gitlab Services

What is a Gitlab CI service ?

The services keyword defines a Docker image that runs during a job linked to the Docker image that the image keyword defines. This allows you to access the service image during build time. - https://docs.gitlab.com/ee/ci/services/

Essentially they are Docker containers we can use in our CI jobs.

package.json

For the examples below assume our package.json scipts section looks something like:

{
  "dev": "svelte-kit dev",
  "e2e": "cypress run --browser chrome",
  "e2e:ci": "start-server-and-test dev http://localhost:3000 e2e"
}

Gitlab CI

Let’s take a look at an example .gitlab-ci.yml file:

stages:
  - test

cypress-e2e-chrome:
  image: cypress/browsers:node14.17.0-chrome88-ff89
  stage: test
  variables:
	BANTER_BUS_CORE_API_MANAGEMENT_API_URL: http://banter-bus-management-api
	BANTER_BUS_CORE_API_DB_HOST: banter-bus-database
	FF_NETWORK_PER_BUILD: 1
	# Hidden the rest of the variables as not to clutter the file
  services:
    - name: mongo:4.4.4
      alias: banter-bus-database
    - name: registry.gitlab.com/banter-bus/banter-bus-core-api:test
      alias: banter-bus-core-api
    - name: registry.gitlab.com/banter-bus/banter-bus-management-api:test
      alias: banter-bus-management-api
    - name: registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest
      alias: banter-bus-database-seed
  script:
	- npm ci
    - export VITE_BANTER_BUS_CORE_API_URL=http://banter-bus-core-api:8080
    - echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf && sysctl -p
    - npm run e2e:ci
  artifacts:
    expire_in: 1 week
    when: always
    paths:
      - cypress/screenshots
      - cypress/videos
    reports:
      junit:
        - results/TEST-*.xml

Services

Let’s break file down a bit, these are essentially all the dependencies of our gui application. We need all of these containers running.

In this case we need four containers (this doesn’t really matter):

  • banter-bus-database: A database for the core-api and management-api
  • banter-bus-core-api: The main API the gui will interact with
  • banter-bus-management-api: Used to help manage our available games, questions etc
  • banter-bus-database-seed: A short lived container which pre-fills the database with some values
services:
  - name: mongo:4.4.4
    alias: banter-bus-database
  - name: registry.gitlab.com/banter-bus/banter-bus-core-api:test
    alias: banter-bus-core-api
  - name: registry.gitlab.com/banter-bus/banter-bus-management-api:test
    alias: banter-bus-management-api
  - name: registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest
    alias: banter-bus-database-seed

In our examples the name field is the image name, this is the same name you’d use when using the docker pull command. The next field is alias this is the name we’ll use to reference that container. This is the container name.

To see how the alias is used, is to look at the environment variables we have provided for the job BANTER_BUS_CORE_API_DB_HOST: banter-bus-database. So core-api will try to connect to database using this host. You can read more about Docker is able to resolve this to an IP address here. Another example is how the URL core-api will use to connect to the management-api BANTER_BUS_CORE_API_MANAGEMENT_API_URL: http://banter-bus-management-api.

ENV Variable One environment variable we must provide is FF_NETWORK_PER_BUILD set to 1 (or true). Docker then creates a bridge network so all the services can communicate amognst themselves. You can read more about it here

We’ve discussed the most important part of the CI file, but lets quickly discuss the rest for completeness

Optional I’ve discussed the main point of this article, how to use services and how to get them to work together.

Variables

We’ve already spoken about this above, but lets take a quick look at the variables section.

variables:
  BANTER_BUS_CORE_API_MANAGEMENT_API_URL: http://banter-bus-management-api
  BANTER_BUS_CORE_API_DB_HOST: banter-bus-database
  FF_NETWORK_PER_BUILD: 1
  # Hidden the rest of the variables as not to clutter the f

These are environment variables that are shared both with the job and the services. Some of these are config passed to the application, such as BANTER_BUS_CORE_API_MANAGEMENT_API_URL and BANTER_BUS_CORE_API_DB_HOST.

Script

Since we are using the cypress/browsers:node14.17.0-chrome88-ff89 image, we have access to chrome (headless) browser we can use with Cypress.

So we can do something like so:

script:
  - npm ci
  - export VITE_BANTER_BUS_CORE_API_URL=http://banter-bus-core-api:8080
  - npm run e2e:ci
  • npm ci: Installs our npm dependencies for the gui app
  • export VITE_BANTER_BUS_CORE_API_URL=http://banter-bus-core-api:8080 exports an enviroment variable which will be used by the gui app so it knows the URL of the core-api. Note the use of the alias name here (and port :8080 default port for the core-api)
  • npm run e2e:ci: Starts the dev server and then runs the cypress test, see start-server-and-test dev http://localhost:3000 e2e where e2e is cypress run --browser chrome

Artifacts

Finally, the artifacts are “things” that are left over after the build. In this case we use them in two ways:

  • One to generate a coverage report with junit
  • Two to save our Cypress screenshots and videos

The downloadable artifacts will expire after 1 week. The Cypress files can be useful when debugging a problem with your tests. You get a video of perhaps why the tests failed.

artifacts:
  expire_in: 1 week
  when: always
  paths:
    - cypress/screenshots
    - cypress/videos
  reports:
    junit:
      - results/TEST-*.xml

Appendix