Docker + Rails 環境にSystem Specを導入する


いろいろ調査しながら対応したのでまとめておきます

環境

rails (6.0.1)
rspec-rails (3.8.2)
capybara (3.29.0)
docker 2.1.0.5 (40693)

手順

gemのインストール

まずはSystem Specに必要なgemをインストールします。

Gemfile
group :test do
  gem 'capybara'
  gem 'selenium-webdriver'
end

もちろん、Rails環境がDockerで動いている場合はコンテナ内で実行する必要があります。

# サービス名がappの場合
$ docker-compose run --rm app bundle install

Docker用スクリプト作成

System Specに必要なchromeをどう用意するかですが、

  1. chromeをrails環境と一緒のイメージでbuildする
  2. chrome用コンテナをdocker-composeに追加する
  3. chrome用コンテナをdockerコマンドで立ち上げる

などなど、いろいろ方法があって、今回は「既存のコンテナに変更を加えなくて良い」という理由で3にしました。
そして、いちいちdockerコマンドを叩くのも面倒なのでスクリプトを作りました。
spec実行時にchromeのdockerコンテナを起動して、spec終了時にコンテナも終了させています。

なお、Dockerイメージにはselenium/standalone-chromeを使っています。
SeleniumHQ/docker-selenium

bin/system_spec
#!/bin/sh

docker run -itd --rm --net=your_app_default --name chrome -p 4444:4444 selenium/standalone-chrome

if [ $# -eq 0 ]; then
  docker-compose run --rm app rspec spec/system
else
  docker-compose run --rm app rspec $*
fi

docker stop chrome

ちなみに--netのネットワーク名はディレクトリ名_defaultとなるので注意してください。
詳しくはreferenceを参照してください。
- Docker run リファレンス — Docker-docs-ja 17.06.Beta ドキュメント
- Compose のネットワーク機能 — Docker-docs-ja 17.06.Beta ドキュメント

以下のように実行します。

# すべてのsystem_specを実行
$ bin/system_spec

# 一部のsystem_specを実行
$ bin/system_spec spec/system/users_spec.rb

System Spec

gemもコンテナも用意できたので、specを書いていきます。

driverでのremote設定、app_host設定あたりがポイントです。

spec/support/capybara.rb
# frozen_string_literal: true

require 'capybara/rspec'

Capybara.register_driver :headless_chrome do |app|
  capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
    chromeOptions: {
      args: %w[--headless --no-sandbox --disable--gpu --window-size=1280x800]
    }
  )
  opts = { desired_capabilities: capabilities, browser: :remote, url: 'http://chrome:4444/wd/hub' }
  Capybara::Selenium::Driver.new(app, opts)
end

Capybara.server_host = '0.0.0.0'
Capybara.javascript_driver = :headless_chrome

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :rack_test
  end

  config.before(:each, type: :system, js: true) do
    driven_by :headless_chrome
    Capybara.app_host = "http://#{Socket.gethostname}"
  end
end

あとはこんなかんじでspecを書いていけばOKです。

spec/system/users_spec.rb
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'Users', type: :system, js: true do
  describe 'ユーザー一覧' do
    subject(:visit_page) { visit '/users' }

    it do
      visit_page
      expect(page).to have_current_path '/users'
    end
  end
end

以上です

参考