LifeHacks, Coliving & Permaculture 🌱

As a ruby developer, I like to write feature tests (or integration tests) using Capybara and Selenium.

VNC Viewer connecting to Selenium standalone debug Docker Compose container

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
You've successfully subscribed to Stephane Bounmy
Welcome back! You've successfully signed in.
Great! You've successfully signed up.
Your link has expired
Success! Your account is fully activated, you now have access to all content.