There are a couple of benefits to running karma tests inside of browsers that are running inside of Docker:
- its easy to test against multiple versions of a browser
- you can test against different browser versions than your build server has installed
- you can test against browsers that aren't even installed on your build server!
In my scenario I wanted to achieve two things:
- testing against a more recent version of Firefox than our build servers had installed
- making the tests more reliable.
Often our Karma tests would fail because Firefox didn't respond within 10s. By "dockerizing" Firefox I hoped to gain some reliability.
Firstly some things to avoid:
Don't build your own Docker containers
You'll need to solve problems like how to install the various browsers latest releases on whatever base OS your container use; you'll either need to maintain these containers or extend your automated builds to include generating the container for each run, which could be quite slow.
In the end I used cypress/browsers:chrome56-ff47 but I probably could have used the selenium images instead.
Don't move your entire Karma build/preprocessing to run inside the Docker container
We have what feels like a relatively large Webpack build pipeline and shifting that from running on the host machine to running in the Docker container was a mistake:
- each container would have an empty yarn cache without some non-trivial config
- introducing Docker adds several layers of additional software complexity to the procedure unnecessarily
- the Docker container would run out of memory while bundling all our
dependenciescode.
The success story:
All that Karma needs to do is launch a new Docker container pointing the relevant browser at the right URL.
NB: for automated builds you'll want to have pulling the Docker image as a prerequisite to the Karma test run as it'll take a while... Alternatively you can guess how long it will take and bump the connection timeouts in your Karma config. We happen to run our tests via gradle so I added a task dependency on a new Gradle Docker Plugin task that pulls the image.
Thankfully Karma can launch a browser via a shell script. Just install the karma-script-launcher plugin and provide an ABSOLUTE path to the script in your browsers array. At the time of writing no errors/warnings are provided by Karma if it can't find the script.
That script should not terminate once the browser has started.
The script receives a single argument that is the URL of the page Karma wants the browser to use. Because we're running our browsers in Docker we need to modify the hostname.
Additionally we also need to shutdown the Docker container when we're done.
So here's what our Firefox shell script looks like:
#!/bin/bash
set -u
set -e
set -o pipefail
URL="$1"
OSNAME=$(uname)
# Fix the URL so the container can access the host
if [[ "$OSNAME" == "Darwin" ]]; then
DOCKER_URL=$( echo "$URL" | sed s/localhost/docker.for.mac.localhost/g | sed s/0.0.0.0/docker.for.mac.localhost/g )
else
DOCKER_URL=$( echo "$URL" | sed s/localhost/172.17.0.1/g | sed s/0.0.0.0/172.17.0.1/g )
fi
echo "Launching firefox: $DOCKER_URL"
# Setup the shutdown/cleanup function
killFirefox() {
docker rm -fv firefox-karma
}
trap "killFirefox; exit 0" SIGKILL EXIT
# Launch!
docker run --rm --name firefox-karma cypress/browsers:chrome63-ff57 firefox -headless "$DOCKER_URL"
That shell script just sits in the same directory as our karma.conf.js
which has this snippet in it:
browsers: [
__dirname + '/docker-browser-firefox.sh',
// __dirname + '/docker-browser-chrome.sh'
],
I tried getting this working with Chrome headless but only got the following message libudev: udev_has_devtmpfs: name_to_handle_at on /dev: Operation not permitted
even when using -v /dev/shm:/dev/shm
as part of the docker run
command and --no-sandbox --disable-gpu
as part of
the browser args.
Summary
Voila! Karma tests running in browsers running in Docker :)
This could probably be packaged up in to a nice NPM module, but I figured that all that it would do was replace the shell script so I wasn't entirely convinced the effort was worth it.
- 09 Oct 2018 » A strange bug on AWS Lambda
- 17 Jan 2018 » How to run Karma tests in browsers in Docker
- 07 Dec 2017 » Switching from Javascript to Typescript
- 30 Oct 2017 » Fun with React event handlers
- 17 Jul 2017 » Switching from Groovy to Java
- 24 May 2017 » Useful Git Aliases
- 27 Mar 2017 » Practical Ratpack Promises
- 03 Nov 2016 » Custom Content in Forms for Confluence Connect
- 04 Oct 2016 » Checking user permissions from REST calls
- 30 Sep 2016 » Using the reflection API in Confluence
- 28 Sep 2016 » Creating a custom Confluence Blueprint
- 06 Sep 2016 » ReactJS in Forms for Confluence Connect
- 25 Apr 2016 » Migrating to ES6 in Atlassian Add-ons
- 17 Mar 2016 » All kinds of things I learnt trying to performance test against Fisheye/Crucible
- 24 Dec 2015 » Adaptavist’s Holiday Gift of Atlassian Deployment Automation
- 17 Dec 2015 » Getting a Custom Field value safely
- 07 Dec 2015 » Putting Google Analytics to work with plugins for Confluence
- 02 Dec 2015 » Devoxx Voting, A retrospective
- 25 Nov 2015 » Some things I've learnt about SingleSelect
- 15 Oct 2015 » Using SOY for JIRA actions
- 26 Sep 2015 » Object Reflection in Groovy
- 22 Sep 2015 » Introducing Adaptavist Labs