Docker + Rails + React + Selenium = 🔥
As a ruby developer, I like to write feature tests (or integration tests) using Capybara and Selenium.
This post is highly inspired by https://medium.com/@jfroom/docker-compose-capybara-selenium-standalone-for-dev-ci-6514bf16d77b I would recommend reading it first.
With few changes :
- App (web) is a SPA (Single Page App) : React
- Api is in JSON using ruby on rails
- Db is in Mongodb
Note I have the following folder in my root repository :
.
..
api/ => Rails app
client/ => React app
data/ => Mongodb data files
docker-compose.yml
README.md
Api and client has their own Dockerfile
Quick overview :
- Mongo runs for both environments dev & test
- Development have http://api:3000 (rails) and http://app:3001 (react)
- Test have http://test_api:3002 (rails), http://test_app:3001 (react) and firefox on 5900
Try building your image without error :
docker-compose build
Now it’s time to tell your your rails_helper.rb to connect to your selenium container’s firefox :
Capybara.default_driver = :remote_firefox
Capybara.default_max_wait_time = 5Capybara.register_driver :remote_firefox do |app|
firefox_capabilities = Selenium::WebDriver::Remote::Capabilities.firefox()
profile = Selenium::WebDriver::Firefox::Profile.new
profile["intl.accept_languages"] = "en-US"
options = Selenium::WebDriver::Firefox::Options.new
options.profile = profile
Capybara::Selenium::Driver.new(app, browser: :remote,
url: "http://#{ENV['SELENIUM_REMOTE_HOST']}:4444/wd/hub", desired_capabilities: firefox_capabilities)
end
Capybara.app_host = "http://test_app:3001"
Now you can run your test :
# specific test
docker-compose run test bundle exec rspec spec/features/...rb
# whole test suite
docker-compose up test
When you bring changes to your docker-compose.yml or dockerfile make sure to turn down properly :
docker-compose down
More…
What if I have also feature tests to run against my rails server ? (api…)
Baiscally you can use rspec metadata and switch Capybara.app_host properly exemple :
RSpec.configure do |config|
# this force to boot rails app
config.before(:suite) do
puts "booting default server..."
ip = `/sbin/ip route|awk '/scope/ { print $9 }'`
ip = ip.gsub "\n", ""
Capybara.server_port = "3000"
Capybara.server_host = ip
Capybara.current_session.server.boot
endconfig.before(:each, type: :feature) do |example|
I18n.locale = :fr #for date picker
if example.metadata[:rails]
Capybara.app_host = "http://#{Capybara.server_host}:#{Capybara.server_port}"
else
Capybara.app_host = "http://test_app:3001"
end
end
end
Now you can tag your spec that hit rails server instead of react :
feature ‘house’, :rails do
scenario 'when creating new house' do
visit '/' #will hit your spawn rails server
end
end
Since my spec suite is dockerized I can use CircleCI right ?
Yeap, here is my config (I’m pretty sure it can be optimized, ex: I dont know why the cache doesnt work properly)
# Ruby CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-ruby/ for more details
#
version: 2
jobs:
build:
machine:
services:
- docker
environment:
RAILS_ENV: test
steps:
# output information about the docker environment
- checkout
- run:
command: docker info
- run:
command: cp ./api/.env.example ./api/.env- run:
command: ls -la
# build our Docker images
- restore_cache:
key: dependency-cache-{{ checksum "docker-compose.yml" }}-{{ checksum "./client/package.json" }}-{{ checksum "./api/Gemfile.lock" }}
- run:
name: "Build app and api image"
command: docker-compose build app api
- run:
name: "Up test_app and test_api container"
command: docker-compose up -d test_app test_api
- save_cache:
key: dependency-cache-{{ checksum "docker-compose.yml" }}-{{ checksum "./client/package.json" }}-{{ checksum "./api/Gemfile.lock" }}
paths:
- ./docker-build
- run:
name: run tests
command: |
mkdir /tmp/test-results
TEST_FILES="$(circleci tests glob "api/spec/**/*_feature.rb"| circleci tests split --split-by=timings)"
docker-compose run test bundle exec rspec \
--format RspecJunitFormatter \
--out /tmp/test-results/rspec.xml \
--format progress \
-- $(sed -e 's/\n/\\n/' -e 's/ /\ /' <<< "${TEST_FILES}")# collect reports
- store_test_results:
path: ~/project/api/tmp/test-results
- store_artifacts:
path: ~/project/api/tmp/test-results
destination: test-results
- store_artifacts:
path: ~/project/api/tmp/capybara