Eirik Sletteberg

@eiriksletteberg

Running karma tests with headless Chrome inside Docker

It can be useful to run build steps inside a Docker container, to handle isolation between environments (prevent conflicts between dependency versions), enable development on different operating systems (macOS, Linux, Windows) and reliable builds (a stable build environment).

There are some obstacles you will encounter when trying to setup such an environment. I went through them, and created an example boilerplate project which shows some of the setup. (Note that I have set up a project which uses yarn instead of npm; it is perfectly possible to create a similar setup that uses npm.)

You can check out the full code example on GitHub:

https://github.com/eirslett/chrome-karma-docker

Adding Google Chrome to the Docker image

From the official node.js image, it’s possible to install Chrome from the official .deb package.

Also note that I add dumb-init; it saves you from a lot of pain and annoyance, like when you’re trying to use Ctrl+C to stop your container.

FROM node:8.2.1

# OPTIONAL: Install dumb-init (Very handy for easier signal handling of SIGINT/SIGTERM/SIGKILL etc.)
RUN wget https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64.deb
RUN dpkg -i dumb-init_*.deb
ENTRYPOINT ["dumb-init"]

# Install Google Chrome
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
RUN
sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
RUN
apt-get update && apt-get install -y google-chrome-stable
WORKDIR /opt/app
ADD package.json yarn.lock /opt/app/
RUN
yarn

Detect Docker, and configure Karma

It turns out there are some problems with running Chrome inside Docker. Unless you’re running the container in privileged mode, Chrome’s sandbox won’t work. We use the is-docker package to detect whether the process is running inside a Docker container or not. Based on that, we’ll have to tweak our build configuration:

const isDocker = require('is-docker')();

module.exports = config => config.set({
customLaunchers: {
ChromeCustom: {
base: 'ChromeHeadless',
// We must disable the Chrome sandbox when running Chrome inside Docker (Chrome's sandbox needs
// more permissions than Docker allows by default)
flags: isDocker ? ['--no-sandbox'] : []
}
}
// more karma configuration here...
}

Karma should be run with the ChromeCustom browser.

Bonus section/off topic: Webpack/webpack-dev-server inside Docker

If you want to run Webpack or webpack-dev-server inside a container, you should use the is-docker detection and tweak a few configurations:

  1. Set host for webpack-dev-server to “0.0.0.0" if running inside a container, so it’s accessible from the outside
  2. Set watchOptions to { aggregateTimeout: 300, poll: 1000 } if running inside a container

Setting up docker-compose

Instead of running docker run with an insane number of arguments, we can use docker-compose.yml to set configuration parameters:

version: '3'

services:
dev:
build:
context:
.
dockerfile: docker/dev/Dockerfile
working_dir: /opt/app
container_name: "dev"
volumes:
- "./:/opt/app"
- /opt/app/node_modules

This will let us run “docker-compose run dev XXX”, which will start the Docker container with “XXX” as a command, for example

  • docker-compose run dev yarn
  • docker-compose run dev yarn run test
  • docker-compose run dev yarn run build

For local development

If it’s possible to work on your project without running inside Docker, that might be preferable, since you can avoid the overhead of containerization. Then, you should be able to “yarn run [whatever-command]” directly.

If you prefer to work on the project within a container, you can use “docker-compose run dev yarn run [whatever-command]” to run inside a container.

Tip: add a helper script “container.sh” to your working directory:

#!/bin/bash
docker-compose run dev $@

Then you can run “./container.sh yarn run [whatever-command]”, which is a bit shorter.

Running on CI

Make sure Docker is installed on the CI instance. (duh!)

You probably want to rebuild the container for every CI run:

docker-compose build

After that, you can run CI build scripts inside the container the same way;

docker-compose run dev yarn run build

That’s all. Good luck with your containerized builds!

More by Eirik Sletteberg

Topics of interest

More Related Stories