It’s not that easy for me

This took me half a day to make it work. I thought it would be easy because in this containerized era, a Rails developer should normally make Rails run in a container, and it’s not convenient to do all your feature tests with a headless browser inside a container, and without any GUI.

In fact, there are some guides in Japanese and English, but somehow most of them did not work out of the box, so I had to do some trial and error.

Result

Here is what works for me.

docker-compose.yml

version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    networks:
      rails-network:
        aliases:
          - db.com
  web:
    build: .
    volumes:
      - .:/my_app
    ports:
      - "3000:3000"
    depends_on:
      - db
    networks:
      rails-network:
        aliases:
          - web.com
  chrome:
    image: selenium/standalone-chrome-debug:3.9.1-actinium
    ports:
      - "4444:4444"
      - "5900:5900"
    depends_on:
      - web
    networks:
      rails-network:
        aliases:
          - chrome.com
networks:
  rails-network:

Dockerfile

FROM ruby:2.6
# libnss3-dev is necessary to install google-chrome & run chromedriver-helper
RUN apt-get update -qq && apt-get install -y postgresql-client libnss3-dev
# Install the newest version of NodeJS
RUN curl -sL https://deb.nodesource.com/setup_11.x | bash -
RUN apt-get install -y nodejs
# Install google-chrome for debian
RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
RUN dpkg -i google-chrome*.deb || apt update && apt-get install -f -y

RUN mkdir /my_app
WORKDIR /my_app
COPY Gemfile /my_app/Gemfile
COPY Gemfile.lock /my_app/Gemfile.lock
RUN bundle install
COPY . /my_app

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]

Gemfile

# frozen_string_literal: true

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "2.6.0"

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem "rails", "~> 5.2.2"

...

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem "byebug", platforms: [:mri, :mingw, :x64_mingw]
  gem "rspec-rails"
end

group :test do
  gem "capybara"
  gem "selenium-webdriver"
  gem "chromedriver-helper"
end

...


spec/rails_helper.rb

# frozen_string_literal: true

# This file is copied to spec/ when you run 'rails generate rspec:install'
require "spec_helper"
ENV["RAILS_ENV"] ||= "test"
require File.expand_path("../../config/environment", __FILE__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require "rspec/rails"
# Add additional requires below this line. Rails is not loaded until this point!
require "capybara/rspec"
require "selenium/webdriver"

# ...

Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

# ...

spec/support/capybara.rb

if ENV["LAUNCH_BROWSER"]
  # To test with browser opened in VNC screen sharing window
  Capybara.configure do |config|
    config.server_host = "web.com"
    config.javascript_driver = :selenium_chrome
  end

  Capybara.register_driver :selenium_chrome do |app|
    Capybara::Selenium::Driver.new(
      app,
      browser: :remote,
      desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome(
        chromeOptions: {
          args: [
            "window-size=1024,768"
          ]
        }
      ),
      url: "http://chrome:4444/wd/hub"
    )
  end
else
  # To test with headless browser inside web container
  Capybara.server = :puma, { Silent: true }

  Capybara.register_driver :chrome_headless do |app|
    options = ::Selenium::WebDriver::Chrome::Options.new

    options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-dev-shm-usage')
    options.add_argument('--window-size=1400,1400')

    Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
  end

  Capybara.javascript_driver = :chrome_headless
end

How to run tests

  • With headless Chrome inside web container:
    docker-compose exec web rspec
    
  • With GUI Chrome browser inside chrome container:
    open vnc://localhost:5900 # To open screen sharing window. Input "secret" if asked for password.
    docker-compose exec -e LAUNCH_BROWSER=true web rspec
    

FYI, when you run rspec test without any specified file or folder, it will run all tests inside spec folder, but Capybara is only called when it comes to feature tests (maybe system tests too, in Rails > 5.1, but I haven’t checked it).

Tags:

TIL, capybara, docker, rails, ruby