M1 MacでRails + Docker + RSpec + CapybaraでのSystemテストの環境構築

[株式会社TECH LUCK](https://techluck.co.jp/?utm_source=qiita&utm_medium=article&utm_campaign=myarticle)という会社で代表兼エンジニアをしている齊藤です。

DXプロジェクト、開発プロジェクト、Rails開発などでお困りごとがありましたら[弊社HP](https://techluck.co.jp/?utm_source=qiita&utm_medium=article&utm_campaign=myarticle)からご相談をいただけますと幸いです。
以下のような問題に対応することが可能です。
- プロジェクトでRailsエンジニアが足りなくて困っている
- Railsのバージョンアップをしたいがノウハウ・リソースが足りなくて困っている
- オフショア開発をしているが、要件の齟齬やコード品質が悪いので改善したい

また、Railsエンジニアも募集しておりますので、興味がありましたら[弊社HP](https://techluck.co.jp/?utm_source=qiita&utm_medium=article&utm_campaign=myarticle)からご連絡いただけますと幸いです。

# 前提
RSpecでsystemテストをする際にDockerでSeleniumを導入して、テストを実行する方法になります。

# Docker関連の設定
```docker-compose.yml
version: '3.7'
services:
  db:
    image: mysql:8.0
    platform: linux/x86_64
    command: --default-authentication-plugin=mysql_native_password
    ports:
      - "4306:3306"
    volumes:
      - db:/var/lib/mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
    security_opt:
      - seccomp:unconfined
  webpacker:
    build: .
    volumes:
      - .:/myapp
      - bundle:/usr/local/bundle
    command: ./bin/webpack-dev-server
    environment:
      WEBPACKER_DEV_SERVER_HOST: 0.0.0.0
    ports:
      - "3035:3035"
  web:
    build: .
    stdin_open: true
    tty: true
    volumes:
      - .:/myapp
      - bundle:/usr/local/bundle
    command: bash -c "rm -rf tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    depends_on:
      - db
    ports:
      - "3000:3000"
    environment:
      WEBPACKER_DEV_SERVER_HOST: webpacker
      SELENIUM_DRIVER_URL: http://selenium_chrome:4444/wd/hub
  selenium_chrome:
    image: seleniarm/standalone-chromium
    ports:
      - "4444:4444"
volumes:
  db:
    driver: local
  bundle:
    driver: local

```

注意する点は`selenium_chrome`のイメージは`selenium/standalone-chrome:latest`を使っている記事が多いです。しかし、M1の場合だとこのイメージだとエラーが出てしまい動きません（これに気づくのに丸2日潰してしまった、、、。）。そのため、`seleniarm/standalone-chromium`のイメージを使います。


`rails_helper.rb`の以下を記述します。
コメントアウトになっていますが、こちらを復活させる形で大丈夫です。

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

`spec/support/`のディレクトリに、`capybara.rb`を作成し以下の記述をします。
これによって`docker-compose`で構築されたコンテナ同士で通信してChromeが使えるようになります。


```spec/support/capybara.rb
require 'capybara/rspec'
require 'selenium-webdriver'

Capybara.register_driver :remote_chrome do |app|
  url = ENV["SELENIUM_DRIVER_URL"]
  capabilities = ::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,
                                 capabilities: capabilities)
end

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

  config.before(:each, type: :system, js: true) do
    Capybara.server_host = IPSocket.getaddress(Socket.gethostname)
    Capybara.server_port = 4444
    Capybara.app_host = "http://#{Capybara.server_host}:#{Capybara.server_port}"
    driven_by :remote_chrome
  end
end
```


# GitHub Actionsへの対応
CI/CDでテストを実現するために、GitHub Actionsを利用する場合の設定を書きます。
GitHub Actionsで立ち上がるコンテナにChromeをインストールして利用する形にします。

以下の記述を追加します。
```.github-action.yml
- name: Chrome Install
  run: sudo apt-get install google-chrome-stable
```

その後、`capybara.rb`を変更します。

```capybara.rb
require 'capybara/rspec'

Capybara.register_driver :remote_chrome do |app|
  url = ENV['SELENIUM_DRIVER_URL']
  capabilities = ::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,
                                 capabilities: capabilities)
end

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

  config.before(:each, type: :system, js: true) do
    if ENV['SELENIUM_DRIVER_URL']
      Capybara.server_host = IPSocket.getaddress(Socket.gethostname)
      Capybara.server_port = 4444
      Capybara.app_host = "http://#{Capybara.server_host}:#{Capybara.server_port}"
      driven_by :remote_chrome
    else
      driven_by :selenium_chrome_headless
    end
  end
end
```

これで環境変数に`SELENIUM_DRIVER_URL`がある場合、つまり、`docker-compose`で起動したrailsコンテナの中では、Dockerコンテナで立ち上がっているSelenium（remote_chrome）を使う形になります。
環境変数に`SELENIUM_DRIVER_URL`がない場合、つまりローカルでの開発の場合は、Github Actions内ではその環境内にインストールされているChromeを使う形になります。


# 参考文献
https://github.com/willnet/capybara-readme-ja

https://qiita.com/ngron/items/f61b8635b4d67f666d75