Capybara + Selenium WebDriver による E2E テストで Net::ReadTimeout への対応（ヘッドレス Chrome の設定例）

Capybara + Selenium WebDriver による E2E テストを実行しているとき Net::ReadTimeout が発生することがある。

```
Net::ReadTimeout

--- Caused by: ---
IO::EAGAINWaitReadable:
Resource temporarily unavailable - read would block
```

これへの対応として、

* (1) タイムアウトをのばす
* (2) リトライする
* (3) そもそもタイムアウトに引っ掛かるような処理を見直す

があると思う。(3) については各位がんばりましょうということで、ここでは (1) (2) について書く。

## 環境

* Rails 4.2.5（がんばる...）
* capybara 2.6.2
* selenium-webdriver 3.8.0
* Google Chrome 63.0.3239.84
* ChromeDriver 2.34.522932

## タイムアウトをのばす

https://github.com/SeleniumHQ/selenium/wiki/Ruby-Bindings#internal-timeouts のとおり

```ruby
client = Selenium::WebDriver::Remote::Http::Default.new
client.read_timeout = 120 # seconds
driver = Selenium::WebDriver.for :remote, http_client: client
```

みたいなことをやれば良いのだけれど、これをみても、なるほどわからん、という人はいると思うので、もう少し周辺も含めた設定例を書いておく。

### ヘッドレス Chrome を使った例

PhantomJS が最新の JS / CSS へ追随しなくなって、E2E テストで使うブラウザーを Chrome へ乗り換えた人も多いと思うので、ヘッドレス Chrome の設定例を書いておく。

```ruby
# spec/support/capybara.rb

require 'capybara/rails'
require 'capybara/rspec'
require 'selenium-webdriver'

client = Selenium::WebDriver::Remote::Http::Default.new
client.read_timeout = 120 # instead of the default 60

Capybara.register_driver :selenium do |app|
  Capybara::Selenium::Driver.new(
    app,
    browser: :chrome,
    desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome(
      chrome_options: {
        args: ['headless', 'window-size=1280,768'],
      },
    ),
    http_client: client,
  )
end

Capybara.javascript_driver = :selenium
```

### ポイント

* `timeout=` メソッドは deprecated
  * `client.read_timeout = xxx` としている箇所、少し古いドキュメントだと `client.timeout = xxx` と書かれているものもあるけど、timeout= メソッドは deprecated になっている
  * Selenium の Wiki については [先ほど](https://github.com/SeleniumHQ/selenium/pull/3264#issuecomment-355910148) 中の人に直してもらえた（感謝）
* ヘッドレス Chrome の `disable-gpu` オプションは不要
  * 本件のメインの内容ではないけれど
  * [ヘッドレス Chrome ことはじめ | Web | Google Developers](https://developers.google.com/web/updates/2017/04/headless-chrome?hl=ja) によると、Chrome 59 の時点では `disable-gpu` が暫定的なフラグとして必要だったようだが、少なくとも Chrome 63 ではこのフラグを付けなくても正常に動く
* ヘッドレス Chrome を RSpec + Capybara で使うことについては下記が分かりやすい
  * [RSpec の feature spec でヘッドレス Chrome を使う - Speee DEVELOPER BLOG](http://tech.speee.jp/entry/2017/06/15/135636)

## リトライする

[rspec-retry](https://github.com/NoRedInk/rspec-retry) という便利な Gem があるのでオススメ。

### 設定例

使い方は公式の README に分かりやすく書いているので特に困らないと思うけれど、参考までに [転職ドラフト](https://job-draft.jp/) で使っている設定を載せておく。

```ruby
# spec/support/retry.rb

require 'rspec/retry'

RSpec.configure do |config|
  config.verbose_retry = true
  config.display_try_failure_messages = true

  # run retry only on features
  config.around :each, :js do |example|
    # 公式では 3 となっているけれど、日本には三度目の正直という言葉があるし、
    # 3回やってもこけるテストは根本的に対応したほうが良いと考えた
    example.run_with_retry retry: 2
  end

  # callback to be run between retries
  config.retry_callback = proc do |example|
    # run some additional clean up task - can be filtered by example metadata
    Capybara.reset! if example.metadata[:js]
  end
end
```

### ポイント

```ruby
config.exceptions_to_retry = [Net::ReadTimeout]
```

とやって、リトライする対象のエラーを指定する方法もある（詳細は公式 README 参照）が、JavaScript を利用した E2E テストは経験上なんやかんやでこけるときがあるので特にエラーを限定しなくて良いのかなと判断した。