Rails6 のちょい足しな新機能を試す73（driven_by編）

# はじめに

（多分）Rails 6 に追加された新機能を試す第73段。 今回は、 `driven_by` 編です。
Rails 6 では、 System Test で使われる `driven_by` メソッドがブロックを受け付けることにより、ブラウザ特有の設定ができるようになりました。

Ruby 2.6.3, Rails 6.0.0.rc2, RSpec(rspec-rails) 3.8.2 で確認しました。
なお、今回は、ブラウザ実行の環境として、 [selenoid/chrome](https://hub.docker.com/r/selenoid/chrome/dockerfile) を利用しました。
ChromeDriver と Google Chrome のバージョンは以下の通りです。

(Rails 6.0.0 がリリースされましたが、動作確認当時の最新版は  Rails 6.0.0.rc2 でした。悪しからず :bow:)

```shell
$ chromedriver --version
ChromeDriver 76.0.3809.68 (420c9498db8ce8fcd190a954d51297672c1515d5-refs/branch-heads/3809@{#864})
$ google-chrome --version
Google Chrome 76.0.3809.87
```

Rails 6.0.0.rc2 は `gem install rails -v 6.0.0rc2 --prerelease` でインストールできます。

```shell
$ rails --version
Rails 6.0.0.rc2
```

今回は、 [Docker で RSpec の System Spec を実行するための設定メモ](https://qiita.com/suketa/items/d783ac61c2a3e4c16ad4) をベースに話を進めます。

ブラウザの User Agent の情報を画面に表示する機能をテストしてみたいと思います。
Google Chrome には、デバイス名を変更することで、 User Agent の情報を変更する機能があるので、それを使ってテストを書いてみたいと思います。
なお、テストは、RSpec を使います。


# docker-compose.yml を変更する

[Docker で RSpec の System Spec を実行するための設定メモ](https://qiita.com/suketa/items/d783ac61c2a3e4c16ad4) では、 [selenium/standalone-chrome-debug](https://hub.docker.com/r/selenium/standalone-chrome-debug) を使ったのですが、今回の目的を満足するように、うまく動作してくれませんでした。
[selenoid/chrome](https://hub.docker.com/r/selenoid/chrome/dockerfile) を利用するように変更します。

```yaml:docker-compose.yml
version: '3'

services:
  web:
    ...
    environment:
      ...
      - "SELENIUM_DRIVER_URL=http://chromedriver:4444"
  chromedriver:
    image: selenoid/chrome
    ports:
      - "4444:4444"
  ....
```

# Controller と View を作る

Controller と View を作ります。

```shell
$ bin/rails g controller home index
```

# Controller を変更する

`request` から `user_agent` を取得するようにします。

```ruby:app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    @user_agent = request.user_agent
  end
end
```

# View を変更する

View で User Agent の情報を表示するように変更します。

```erb:app/views/home/index.html.erb
<h1>Home#index</h1>

<p>
  <%= @user_agent %>
</p>
```

# driven_by を使って Chrome で User Agent を切り替える

当初、 RSpec の example の metadata で切り替えようとしたのですが、うまくいかなかった(example 単位で、デバイス名を切り替えられなかった) ので、環境変数 `DEVICE_NAME` を使って、変更できるようにします。

[Docker で RSpec の System Spec を実行するための設定メモ](https://qiita.com/suketa/items/d783ac61c2a3e4c16ad4) で使った `capybara.rb` を修正します。

```ruby:spec/support/capybara.rb
require 'capybara/rspec'

RSpec.configure do |config|
  config.before(:each, type: :system) do |example|
    driven_by :selenium, using: :headless_chrome, screen_size: [1024, 768], options: {
      browser: :remote,
      url: ENV.fetch('SELENIUM_DRIVER_URL'),
      desired_capabilities: :chrome
    } do |driver_option|
      # driver_option.add_emulation(device_name: example.metadata[:device_name])
      driver_option.add_emulation(device_name: ENV.fetch('DEVICE_NAME', nil))
      driver_option.add_argument('no-sandbox')
    end
    Capybara.server_host = 'web'
    Capybara.app_host = 'http://web'
  end
end
```

ここで、 `driven_by` メソッドを呼び出すときにブロックを指定して、 `device_name` の指定と Chrome の引数として `no-sandbox` を指定していることに注意してください。これが、今回のちょい足し機能です。
なお、この `driven_by` メソッドは、rspec-rails に含まれるメソッドです。
rspec-rails の `driven_by` メソッドは、Rails 6 の `ActionDispatch::SystemTestCase.driven_by` のラッパーになっています。`

```ruby:lib/rspec/rails/example/system_example_group.rb
# rspec-rails (lib/rspec/rails/example/system_example_group.rb)より抜粋
...
        def driven_by(*args, &blk)
          @driver = ::ActionDispatch::SystemTestCase.driven_by(*args, &blk).tap(&:use)
        end
...
```

# system spec を書く

system spec を書いてみます。
User Agent の一部が index ページに表示されるかどうかテストします。
今回は、意図的に iPhone と HeadlessChrome の2つのパターンだけを想定した書き方にしました。


```ruby:spec/system/home_spec.rb
require 'rails_helper'

RSpec.describe type: :system do
  # using :device_name metadata does not work fine.
  # describe 'when iPhone 8', device_name: 'iPhone 5' do
  #   it do
  #     expect(page).to have_content 'iPhone'
  #   end
  # end
  describe 'index' do
    before do
      visit home_index_path
    end

    let(:user_agent_pattern) do
      device_name = ENV.fetch('DEVICE_NAME', nil)
      case device_name
      when /iphone/i
        'iPhone'
      else
        'HeadlessChrome'
      end
    end

    it 'show user agent' do
      expect(page).to have_content user_agent_pattern
    end
  end
end
```

# spec を実行する

spec を実行してみます。

```shell
$ bundle exec rspec
...
Finished in 1.39 seconds (files took 2 seconds to load)
1 example, 0 failures
```

環境変数で `iPhone 7` を指定してみます。

```shell
$ env DEVICE_NAME='iPhone 7' bundle exec rspec'
...
Finished in 1.38 seconds (files took 2.01 seconds to load)
1 example, 0 failures
```

今度は、 `Galaxy S5` を指定してみます。
実際に表示されている User Agent の情報には、 HeadlessChrome は含まれず、 User Agent が異なっている(Android が含まれている)ことがわかりますね。


```shell
$ env DEVICE_NAME='Galaxy S5' bundle exec rspec
...
Failures:

  1) {:type=>:system} index show user agent
       Failure/Error: expect(page).to have_content user_agent_pattern
              expected to find text "HeadlessChrome" in "Home#index\nMozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3329.0 Mobile Safari/537.36"



     # ./spec/system/home_spec.rb:30:in `block (3 levels) in <top (required)>'

     Finished in 3.64 seconds (files took 2.03 seconds to load)
     1 example, 1 failure'`
```

# 問題点

直接、今回の機能を試すこととは関係ない（と思われる）話ですが...。
spec が失敗したときにブラウザのスクリーンショットが取られるのですが、スクリーンショットは白い画面になるだけで、何も取れていませんでした。原因がわかっていません。

# 試したソース

試したソースは以下にあります。
https://github.com/suketa/rails_sandbox/tree/try073_add_browser_capabilities


# 参考情報

* [What is new in Rails 6.0](https://bogdanvlviv.com/posts/ruby/rails/what-is-new-in-rails-6_0.html)
* [[UPDATED] Implement a way to add browser capabilities](https://github.com/rails/rails/pull/35081)