How to Run Cypress in Docker With a Single Command

May 2, 2019

By Gleb Bahmutov

If you are a Node.js developer, installing Cypress as a dev dependency in your package.json file or even via direct download seems simple enough. Yet for developers working in other languages like Python or Go, using npm can be an obstacle. We often hear this question from developers:

Why don’t you have a Docker image with Cypress pre-installed?

Having an image with both OS dependencies and the Cypress test runner ready to go seems to be very convenient. We have listened and created a Docker image with Cypress installed. It is called cypress/included and is tagged with the version of Cypress installed in the image. For now, we'll use the image for the current Cypress version as of this writing - the cypress/included:3.2.0 image. We create new images for every Cypress version released. The image is built on top of the cypress/base:12.1.0 image with Node 12.1.0 included. You can find our official Docker images in the cypress-docker-images repository and on Docker hub.

You can find the examples below in the repo demo-docker-cypress-included.

Running headless tests

If you have Cypress end-to-end tests, you can run them using the complete image. For example, if your project structure looks like this:

cypress/
  integration/
    spec.js
cypress.json

Then you can execute your Cypress tests using the following shell command:

$ docker run -it -v $PWD:/e2e -w /e2e cypress/included:3.2.0

==============================================================================

  (Run Starting)

  ┌──────────────────────────────────────────────────────────────────────────┐
  │ Cypress:    3.2.0                                                        │
  │ Browser:    Electron 59 (headless)                                       │
  │ Specs:      1 found (spec.js)                                            │
  └──────────────────────────────────────────────────────────────────────────┘

──────────────────────────────────────────────────────────────────────────────

  Running: spec.js...                                                 (1 of 1)


  Cypress TodoMVC test
    ✓ adds 2 todos (1351ms)

  ...
Explanation of the "docker run" command line arguments

 -it          = interactive terminal
 -v $PWD:/e2e = map current folder to /e2e inside the container
 -w /e2e      = set working directy to /e2e

Nothing to install, just write your spec files using your favorite editor and run them.

Commands and options

The image cypress/included:3.2.0 has the entrypoint set to cypress run, so you don’t need to type it when running our Docker image. If you want a different command, you can change the entrypoint and then pass any additional arguments after the image name.

$ docker run -it -v $PWD:/e2e -w /e2e --entrypoint=cypress cypress/included:3.2.0 help

  Usage: cypress [options] [command]

  Options:

    -v, --version      prints Cypress version
    -h, --help         output usage information

  Commands:

    help               Shows CLI help and exits
    version            prints Cypress version
    run [options]      Runs Cypress tests from the CLI without the GUI
    open [options]     Opens Cypress in the interactive GUI.
    install [options]  Installs the Cypress executable matching this package's version
    verify             Verifies that Cypress is installed correctly and executable
    cache [options]    Manages the Cypress binary cache

You can also pass environment variables into the container to control Cypress behavior. For example, the boolean config option video controls if the video of the run is recorded. It is true by default, but you can disable it via an environment variable.

$ CYPRESS_VIDEO=false
$ docker run -it -v $PWD:/e2e -w /e2e -e CYPRESS_VIDEO cypress/included:3.2.0

If you want to record the test run on the Cypress Dashboard to review later, you need to pass the record key and the --record CLI flag.

$ docker run -it -v $PWD:/e2e -w /e2e \
  -e CYPRESS_RECORD_KEY cypress/included:3.2.0 --record

Container info

To show the information about the operating system and pre-installed browsers, you can execute cypress info command:

$ docker run -it -v $PWD:/e2e -w /e2e --entrypoint=cypress cypress/included:6.2.1 info
Displaying Cypress info...

Detected 2 browsers installed:

1. Chrome
  - Name: chrome
  - Channel: stable
  - Version: 87.0.4280.66
  - Executable: google-chrome

2. Firefox
  - Name: firefox
  - Channel: stable
  - Version: 82.0
  - Executable: firefox

Note: to run these browsers, pass <name>:<channel> to the '--browser' field

Examples:
- cypress run --browser firefox
- cypress run --browser chrome

Learn More: https://on.cypress.io/launching-browsers

Proxy Settings: none detected
Environment Variables:
CYPRESS_CACHE_FOLDER: /root/.cache/Cypress

Application Data: /root/.config/cypress/cy/development
Browser Profiles: /root/.config/cypress/cy/development/browsers
Binary Caches: /root/.cache/Cypress

Cypress Version: 6.2.1
System Platform: linux (Debian - 10.5)
System Memory: 2.09 GB free 534 MB

We can see both Chrome and Firefox browsers pre-installed in the cypress/included:6.2.1 image. Let's run our tests using Firefox browser for example:

$ docker run -it -v $PWD:/e2e -w /e2e cypress/included:6.2.1 --browser firefox

========================================================================

  (Run Starting)

  ┌──────────────────────────────────────────────────────────────────────┐
  │ Cypress:    6.2.1                                                    │
  │ Browser:    Firefox 82                                               │
  │ Specs:      1 found (spec.js)                                        │
  └──────────────────────────────────────────────────────────────────────┘
  ...

Interactive mode

Running the tests inside a Docker container is nice, but it is missing my favorite Cypress feature: its interactive Test Runner with the Command Log, time-traveling debugger and live view of what is going on during the test! Typically, you would execute cypress open to open the Test Runner in interactive mode, but how do we see it if Cypress opens inside a Docker container?

If you want to see Cypress in interactive mode, you need to forward the XVFB messages from Cypress out of the Docker container into an X11 server running on the host machine. I have done this on my Mac; other operating systems might require different commands.

I have installed an XQuartz X11 server following the instructions at Running GUI applications using Docker for Mac.

Then I grabbed the IP of the host machine and added it to the allowed X11 hosts.

$ IP=$(ipconfig getifaddr en0)
$ /usr/X11/bin/xhost + $IP
10.0.0.124 being added to access control list

Now you can execute a cypress open command passing DISPLAY and the X11 socket file to the container:

DISPLAY=$IP:0
docker run -it \
  -v $PWD:/e2e \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  -w /e2e \
  -e DISPLAY \
  --entrypoint cypress \
  cypress/included:3.2.0 open --project .
Explanation of the "docker run" command line arguments:

 -it                  = interactive terminal
 -v $PWD:/e2e         = map current folder to /e2e inside the container
 -v /tmp/.X11-unix:/tmp/.X11-unix = map X11 socket file to communicate
 -w /e2e              = set working directy to /e2e
 -e DISPLAY           = pass environment variable DISPLAY to the container
 --entrypoint cypress = run "cypress" command
    with arguments AFTER Docker image name
    in our case they are "--project ." to point globally installed Cypress
    at the current working directory /e2e inside the container

The Docker container starts and you can see the full interactive Cypress Test Runner open. You can watch the test run, interact with the Command Log, open DevTools, etc. Even spec file watching is working - if you edit and save the cypress/integration/spec.js file, the Test Runner picks up the change and reruns the tests.

Debugging tip: if Cypress shows an error Gtk-WARNING **: cannot open display:... make sure X11 server allows connections over the network from the Docker container. Run xhost command in the terminal to see if it has the IP address you have added previous with xhost + $IP.

Docker compose

Using docker-compose to spawn services and run end-to-end tests is very convenient. We have coded several examples that show how to run a web application and Cypress tests in two Docker containers:

To support both cypress run and cypress open settings we recommend:

  • placing the default settings for cypress run into docker-compose.yml file, for example, like this
# e2e/docker-compose.yml from repo
# https://github.com/bahmutov/cypress-open-from-docker-compose
version: '3.2'
services:
  # this is the web application we are going to test
  sentimentalyzer:
    build: ../
    environment:
      - PORT=8123
  # Cypress container
  cypress:
    # the Docker image to use from https://github.com/cypress-io/cypress-docker-images
    image: "cypress/included:3.2.0"
    depends_on:
      - sentimentalyzer
    environment:
      # pass base url to test pointing at the web application
      - CYPRESS_baseUrl=http://sentimentalyzer:8123
    # share the current folder as volume to avoid copying
    working_dir: /e2e
    volumes:
      - ./:/e2e

You can start the application, run the headless tests and close the services with command:

docker-compose up --exit-code-from cypress
  • place the X11 configuration that enables cypress open Test Runner to show on the host machine in a separate YAML file that extends the above file. The second file only has additional environment variables and volumes
version: '3.2'
# e2e/cy-open.yml from repo
# https://github.com/bahmutov/cypress-open-from-docker-compose
services:
  cypress:
    # pass custom command to start Cypress otherwise it will use the entrypoint
    # specified in the Cypress Docker image.
    # also pass "--project <folder>" so that when Cypress opens
    # it can find file "cypress.json" and show integration specs
    # https://on.cypress.io/command-line#cypress-open
    entrypoint: cypress open --project /e2e
    environment:
      # get the IP address of the host machine and allow X11 to accept
      # incoming connections from that IP address
      #   IP=$(ipconfig getifaddr en0)
      #   /usr/X11/bin/xhost + $IP
      # then pass the environment variable DISPLAY to show Cypress GUI on the host system
      #   DISPLAY=$IP:0
      - DISPLAY
    volumes:
      # for Cypress to communicate with the X11 server pass this socket file
      # in addition to any other mapped volumes
      - /tmp/.X11-unix:/tmp/.X11-unix

To start in the interactive mode we need to pass both filenames to the docker

docker-compose -f docker-compose.yml -f cy-open.yml up --exit-code-from cypress

You should see the Test Runner and be able to run tests

Testing site on host

Let's consider another common situation: running the Test Runner inside a Docker container, while running the website on the host outside the container. First, start the website on the host machine

$ npm start
# website is running at http://localhost:2222

Now start the Test Runner but instead of localhost use a special Docker domain that points back at the host machine:

DISPLAY=$IP:0
docker run -it \
  -v $PWD:/e2e \
  -w /e2e \
  -e DISPLAY \
  --entrypoint cypress \
  cypress/included:3.2.0 open --project . \
  --config baseUrl=http://host.docker.internal:2222
Explanation of the "docker run" command line arguments:

 -it                  = interactive terminal
 -v $PWD:/e2e         = map current folder to /e2e inside the container
 -w /e2e              = set working directy to /e2e
 -e DISPLAY           = pass environment variable DISPLAY to the container
 --entrypoint cypress = run "cypress" command
    with arguments AFTER Docker image name
    in our case they are "--project ." to point globally installed Cypress
    at the current working directory /e2e inside the container
--config baseUrl=...  = replace the default "baseUrl" with 
    special domain pointing to the website running on the host machine

Running the Test Runner in a Docker container allows us to debug font and encoding issues. For example the problem of missing Chinese characters in the cypress-documentation translation is only visible on CI or inside a Docker container.

Missing Chinese characters when running the Test Runner inside the Docker container

Start testing

That should give you all you need to start using Cypress in Docker for all your testing needs. If you were on the fence before due to compatibility concerns, worry no more, and download the Cypress app here.

More information

If you have any feedback for this approach or suggestions on how to make running Cypress even simpler for your team, please let us know. Open an issue in the cypress-io/cypress-docker-images repo or in the main cypress-io/cypress repo (and do not forget to give it a ⭐️!)

Bonus

If you want to run Cypress Test Runner inside a Docker container, while the web application is running on the host machine, read how to do this in "Run Cypress included from Docker container"

Bonus 2

You can use cypress/included Docker image to run tests without installing any dependencies on GH Actions CI. See repository cypress-gh-action-included and its workflow file:

name: included
on: [push]
jobs:
  cypress-run:
    runs-on: ubuntu-latest
    # Docker image with Cypress pre-installed
    # https://github.com/cypress-io/cypress-docker-images/tree/master/included
    container: cypress/included:3.8.3
    steps:
      - uses: actions/checkout@v1
      - run: cypress run