Rails on DockerでRSpecのSystem testをSelenium Dockerを使ってやってみた。

Rails on DockerでRSpecのSystem testをChromeでやるときのメモです。

Rails on Dockerは[こちら](https://docs.docker.com/compose/rails/)などを参考にしてます。
Headless Chromeは今回は[docker-selenium](https://github.com/SeleniumHQ/docker-selenium)のDockerコンテナを使うようにします。

# `docker-compose.yml`を編集する
まずはdocker-seleniumを使えるように`docker-compose.yml`を編集する。
docker-seleniumの中でも[standalone-chrome](https://hub.docker.com/r/selenium/standalone-chrome)を使います。このイメージはChromeが最初からインストールされたものです。

```diff:docker-compose.yml
  version: '3'
  services:
    db:
      image: postgres
      volumes:
        - ./myapp/db:/var/lib/postgresql/data
    web:
      build: .
      command: bundle exec rails s -p 3000 -b '0.0.0.0'
      volumes:
        - .:/myapp
      ports:
        - "3000:3000"
      depends_on:
        - db
+       - chrome
+   chrome:
+     image: selenium/standalone-chrome:latest
+     ports:
+       - 4444:4444
```
`services.web.depends_on`に`chrome`を指定することで、このあと`$ docker-compose run web rspec`でテスト実行する時に`chrome`コンテナが自動で起動するようにしてます。

# RSpecを準備する
次にRails on DockerのアプリケーションにRSpecをインストールします。RSpecのSystem testはversion 3.7以上で動作します。

Gemfileにrspec-railsを追記します。

```ruby:Gemfile
group :development, :test do
  gem 'capybara', '>=2.15'
  gem 'selenium-webdriver'
  gem 'rspec-rails', '~>3.9'
end
```
`bundle install`するためにDockerイメージをビルドします。

```
$ docker-compose build
```
次に、Dockerコンテナを起動させてRSpecをインストールしていきます。

```
$ docker-compose run web rails g rspec:install
```
RSpecがインストールできたら`rails_helper.rb`を編集してRSpec実行時に`docker-selenium`のコンテナのブラウザを使用するように設定します。

```rb:/spec/rails_helper.rb
# ~~~
Capybara.register_driver :remote_chrome do |app|
  url = "http://chrome:4444/wd/hub"
  caps = ::Selenium::WebDriver::Remote::Capabilities.chrome(
    "goog:chromeOptions" => {
      "args" => [
        "no-sandbox",
        "headless",
        "disable-gpu",
        "window-size=1680,1050"
      ]
    }
  )
  Capybara::Selenium::Driver.new(app, browser: :remote, url: url, desired_capabilities: caps)
end

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 :remote_chrome
    Capybara.server_host = IPSocket.getaddress(Socket.gethostname)
    Capybara.server_port = 3000
    Capybara.app_host = "http://#{Capybara.server_host}:#{Capybara.server_port}"
  end
# ~~~
end
```
`remote_chrome`というドライバーを定義して`js: true`の場合利用するようにしています。
画面表示の必要はないので、高速化のために`headless`を採用します（ブラウザの起動がない分高速！）

デフォルトで`rails_helper.rb`を設定ファイルとして読み取るように`.rspec`を書き換えます。

```diff:.rspec
- --require spec_helper
+ --require rails_helper
```
これで設定は完了です！

# System Testを書く
確認のためにテストを実行しておきましょう。まずはspecファイルを作っておきます。

```
$ mkdir spec/system
$ touch spec/system/sample_spec.rb
```
`type`に`system`、`js`に`true`を設定することでheadless Chromeでテストすることができます。（`js: true`がない場合は`rack_test`が実行される）

```rb:spec/system/sample_spec.rb
feature 'sample', type: :system, js: true do
  scenario 'sample scenario' do
    # 例えばルートパスにアクセスできることを確認
    visit root_path
    expect(current_path).to eq root_path
  end
end
```

# テストを実行する

```
$ docker-compose run web rspec spec/system/sample_spec.rb
```

# 動いてるの見る
テストをしていると、ちゃんと動作しているのかブラウザ操作をみたいこともあるときとおもいます。その場合はselenium-chromeの代わりに[selenium-chrome-debug](https://hub.docker.com/r/selenium/standalone-chrome-debug)を使います。
`docker-compose.yml`を以下のように、Dockerイメージの変更とポートの追加をします。selenium-chrome-debugでは5900ポートからVNCサーバーにアクセスできるようになっています。

```diff:docker-compose.yml
  version: '3'
  services:
    db:
      image: postgres
      volumes:
        - ./myapp/db:/var/lib/postgresql/data
    web:
      build: .
      command: bundle exec rails s -p 3000 -b '0.0.0.0'
      volumes:
        - .:/myapp
      ports:
        - "3000:3000"
      depends_on:
        - db
        - chrome
    chrome:
-     image: selenium/standalone-chrome:latest
+     image: selenium/standalone-chrome-debug:latest
      ports:
        - 4444:4444
+       - 5900:5900
```
また、上の設定では`headless`を採用しているのでブラウザが起動しないです。
なのでオプションを消しておきましょう。

```diff:spec/rails_helper.rb
  # ~~~
  Capybara.register_driver :remote_chrome do |app|
    url = "http://chrome:4444/wd/hub"
    caps = ::Selenium::WebDriver::Remote::Capabilities.chrome(
      "goog:chromeOptions" => {
        "args" => [
          "no-sandbox",
-         "headless",
          "disable-gpu",
          "window-size=1680,1050"
        ]
      }
    )
    Capybara::Selenium::Driver.new(app, browser: :remote, url: url, desired_capabilities: caps)
  end
  # ~~~
```

ではテストを実行していきましょう！
まず、`chrome`コンテナを立ち上げておき、VNCで接続しておきます。

```
$ docker-compose up -d chrome
$ open vnc://localhost:5900
```
パスワードを求められるので「secret」と入力してください。これでブラウザが立ち上がるサーバーが表示されている状態になりますのでRSpecでテストを実行してみてください。

```
$ docker-compose run web rspec
```
テストに合わせてブラウザが立ち上がり、操作が行われていくのが見れるはずです。

以上、Rails on DockerでRSpecのSystem testをSelenium Dockerを使ってやってみた、でした。

# Reference
- [Rails on Docker: System Specs in Containers with RSpec, Capybara, Chrome, and Selenium — Plymouth Software](https://www.plymouthsoftware.com/articles/rails-on-docker-system-specs-in-containers-with-rspec-capybara-chrome-and-selenium)
- [Capabilities & ChromeOptions - ChromeDriver - WebDriver for Chrome](https://chromedriver.chromium.org/capabilities)