Rails on Docker: System Specs in Containers with RSpec, Capybara, Chrome, and Selenium
Written by Chris • 25 February 2020
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!