/ Rails

Testing a Rails & Angular app with Cucumber and Capybara with Docker

This article will explain you how to test a Ruby On Rails and AngularJs application with Cucumber all using Docker containers.

Testing a Rails application using JavaScript has always something hard due to the fact that Capybara needs to start a second thread than the one where the tests are running in order to execute the Javascript part.

Two threads, one database

The first thread is running the cucumber scenario for example, where you'll create a User, some other objects and then login, click here and there and so on, and the second thread is where Capybara is piloting the web browser through a driver like poltergeist.

The problem with the 2 threads is that the Rails database connections aren't shared so that the second thread doesn't know when the first one has commited some data, or when DatabaseCleaner rollbacked the transaction at the end of your scenario.

This will lead you where data is missing or some remaining data from a previous test is present having as a consequence to have random failing test suite (the test is passing when running alone but fails when the suite is running, or even worst it is failing on the CI but not your local machine).

Let's share the database connections

You probabely read about a patch sharing the ActiveRecord connections between the 2 threads, you run your tests and it's green!

So what's the problem?

Okay then you run the entire suite and here you have concurrency issues with the database:

Mysql2::Error: This connection is in use by: #<Celluloid::Thread:0x0000000d1b56e0 sleep>

There seems to be a way to avoid this issue, I didn't tried yet, but the 100% working solution is to not use transactionnal tests, but it slows your test suite a lot 😢.

In your features/env.rb file, change the DatabaseCleaner strategy to :truncation:

# Remove/comment out the lines below if your app doesn't have a database.
# For some databases (like MongoDB and CouchDB) you may need to use :truncation
# instead.
begin
  DatabaseCleaner.strategy = :truncation
rescue NameError
  raise 'You need to add database_cleaner to your Gemfile ' \
        '(in the :test group) if you wish to use it.'
end

Also ensure the Cucumber::Rails::Database.javascript_strategy is also set to :truncation.

Testing the AngularJs application

My first try was to install poltergeist like I always did in the past and test the AngularJs app.

Unfortunately it didn't worked, the AngularJs app wasn't loading plus the PhantomJS maintainer decided to step down (also here), so I need something else.

Fortunately Google has released the Chrome Driver allowing to use the Chrome headless mode for running tests.

As I'm using Docker, I first look around for a Docker image which could help me and I found the SeleniumHQ/docker-selenium Docker image (Github repo).

This image contains Selenium, Firefox and Chrome all configured in order to allow you to run your tests 🎉.
Plus they have a debug image which includes a VNC server allowing you to see the web browser and having a control access, just perfect!

Capybara with Selenium and Chrome headless

Here is how to configure Capybara in your Rails application so that it uses Selenium to execute the Cucumber scenarios.

This config is registering a new Capybara driver named :chrome using the selenium-webdriver gem to define the capabilities to configure Chrome (flags from :args and :prefs) also increase the timeout and finally defines the URL where is running the selenium node (in this case the hostname is selenium which is the container name, Docker adds a DNS entry to resolve containers IP from their names).

Next we are configuring the Capybara.javascript_driver in order to use our new Capybara driver.

The Capybara.default_driver = Capybara.javascript_driver line makes the javascript driver as default, meaning all the tests require the Javascript mode, so no need to tag the Cucumber scenarios or features.

A very important line is Capybara.server_host = '0.0.0.0' # bind to all interfaces.
Without it, Capybara is binding to the local loop 127.0.0.1 and Chrome can't reach the app.

Bonus: The capybara-screenshot gem is working so that when the test is failing (or you request yourself), HTML and PNG screenshots are generated in the tmp/capybara folder of your project.

Docker compose

Finally you need to add the Docker image containing Selenium and the web browsers:

Add the following block to your docker-compose.yml file:

version: "2"

services:
  ...
  selenium:
    links:
      - app:one-test.pharmony.eu
    image: selenium/standalone-chrome:3.11.0-antimony
    # image: selenium/standalone-chrome-debug:3.11.0-antimony # For Debug (And uncomment the `ports` node to connect with VNC)
    # ports:
    #   - "5900:5900"
    volumes:
      - /dev/shm:/dev/shm

And also add the environment variable in your app container:

version: "2"

services:
  app:
    ...
    environment:
      ...
      - SELENIUM_PORT=4444

You're ready to go! Now run the docker-compose up command and it will download the image and start it.

(FYI as you updated the environment variables of your app image, you need to stop, remove and create it again in order to get the new variable in your container)

Debug mode

Now you want to run the tests and see the Chrome app running?

In your docker-compose.yml file comment the image: selenium/standalone-chrome:3.11.0-antimony line from the selenium service and uncomment the lines after (image and ports).

Stop and remove the selenium container and start it again.

Now you can connect to the VNC server at vnc://localhost:5900 using the Screen Sharing app.

If you want to have the Chrome devtools opened automatically, uncomment the --auto-open-devtools-for-tabs flag from the capybara configuration file.