Rails on Docker: System Specs in Containers with RSpec, Capybara, Chrome, and Selenium

System tests in Rails are a great way to test several aspects of your application. They also allow tests to be written in a user-centric way, focussing on user outcomes rather than technical implementation.

However, whilst Rails 5 makes system specs easy to configure in a standard environment, configuring them in a containerised environment can be a little trickier - even more so if you are using RSpec rather than Rails’ default minitest framework.

At Plymouth Software, we are using the standalone Selenium Grid Chrome image to provide a container with chrome pre-installed, and configuring the test suite to use that container.

This removes the need for our application’s Dockerfile to install Chrome, whilst allowing us to run system specs in any CI environment that supports Docker.

There are a lot of moving parts in system specs, so for reference this is how we are configuring our applications:

# Gemfile
group :test do
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
end

Note: Remove the webdrivers gem from your Gemfile. If webdrivers is present, it will attempt to find Chrome in your application’s container. As Chrome isn’t installed in the Dockerfile, the spec will fail.

# docker-compose.yml

services:
  web:
    environment:
      HUB_URL: http://chrome:4444/wd/hub # <-- Add the HUB_URL environment variable
    depends_on:
      - chrome # <-- Link to the chrome container
    # ...

  chrome:
    image: selenium/standalone-chrome:3.141.59-zirconium # this version should match that of the selenium-webdriver gem (see Gemfile)
    volumes:
      - /dev/shm:/dev/shm

Next we need to register a new driver with Capybara that is configured to use the Selenium Grid container when a HUB_URL environment variable is present:

# spec/rails_helper.rb
Capybara.register_driver :chrome_headless do |app|
  chrome_capabilities = ::Selenium::WebDriver::Remote::Capabilities.chrome('goog:chromeOptions' => { 'args': %w[no-sandbox headless disable-gpu window-size=1400,1400] })

  if ENV['HUB_URL']
    Capybara::Selenium::Driver.new(app,
                                   browser: :remote,
                                   url: ENV['HUB_URL'],
                                   desired_capabilities: chrome_capabilities)
  else
    Capybara::Selenium::Driver.new(app,
                                   browser: :chrome,
                                   desired_capabilities: chrome_capabilities)
  end
end
# ...
RSpec.configure do |config|
  # ...
  config.before(:each, type: :system) do
    driven_by :chrome_headless

    Capybara.app_host = "http://#{IPSocket.getaddress(Socket.gethostname)}:3000"
    Capybara.server_host = IPSocket.getaddress(Socket.gethostname)
    Capybara.server_port = 3000
  end
end

With the configuration set up, we can create a simple system spec to test that everything is working:

# spec/system/home_page_spec.rb

require 'rails_helper'

RSpec.describe 'User visits site', type: :system do
  it 'visits page' do
    visit '/'
    expect(page).to have_text('Hello World')
  end
end

To run the spec, use Docker Compose. Remember to set the RAILS_ENV value to test:

$ docker-compose run --rm -e RAILS_ENV=test web bin/rails spec:system

All being well, the test should pass. If it does not, a screenshot will be saved into your application’s tmp/screeenshots folder.

Although we’ve not tested it yet, Selenium Grid also provides a standalone Firefox container which can be used in the same way to allow testing your application on Firefox.


Thanks to Niall for helping with the configuration!

Previous
Previous

Rails with Vue: Your First Component

Next
Next

Supporting Plymouth's Mayflower Forest