Capybara::ElementNotFound 回避に wait させるか retry させるか

[Capybara](https://github.com/teamcapybara/capybara) と [PhantomJS (Poltergeist)](https://github.com/teampoltergeist/poltergeist) を使い Ajax 処理をテストする際に、結果取得が間に合わずにテストに失敗するケースがあります。

```
Failure/Error: find('.alert-success', text: "Success!")

Capybara::ElementNotFound:
  Unable to find css ".alert-success" with text "Success!"
```

# TL;DR
- capybara の `wait` 時間を延ばしたが解決しなかった
- 特定のテストを再実施する [rspec-retry](https://github.com/NoRedInk/rspec-retry) を使ったところ問題が解決した

# Capybara は非同期処理とどう向き合っているのか

Capybara の README にはこんな記述があります。
> Capybara is smart enough to retry finding the link for a brief period of time before giving up and throwing an error. 
> [Asynchronous JavaScript (Ajax and friends)](https://github.com/teamcapybara/capybara/blob/4895195726a355810b523b3f96b1aae43bcb4c53/README.md#asynchronous-javascript-ajax-and-friends)

実際の処理は`Capybara::Node::Base#synchronize` で行われているようです。ここには更に詳細なコメントがありました。
> As long as any of these exceptions are thrown, the block is re-run, until a certain amount of time passes. The amount of time defaults to {Capybara.default_max_wait_time} and can be overridden through the `seconds` argument.
> [capybara/base.rb](https://github.com/teamcapybara/capybara/blob/03092d5c7f3673d68548ee6e36ea1978449ecac5/lib/capybara/node/base.rb#L42-L99)

# 非同期処理が終わるまでリトライしてもらう方法

`Capybara.default_max_wait_time` のデフォルト値は2秒に設定されているので、こいつを延ばせば良さそうです。

## 方法1. グローバル設定値を変更する
`spec_helper.rb` に追記する方法です。

```
Capybara.default_max_wait_time = 5
```

## 方法2. オプション引数で渡す
`Capybara::Node::Finders` のメソッドはオプション引数を受け付けています。この中に `wait` というものがあります。
 
[Module: Capybara::Node::Finders — Documentation for jnicklas/capybara (master)](http://www.rubydoc.info/github/jnicklas/capybara/Capybara/Node/Finders#find-instance_method)

```
find('.alert-success', text: "Success!", wait: 5)
```

## 方法3. rspec-retry で特定のテストを再実施する
上記のオプション引数を渡す方法を使っていたのですが、それでも数回に一回失敗していました。徐々に閾値を上げていったのですが、 `wait: 30` でもまだ失敗するので別の方法を模索しました。

そこで思いついたのが特定のテストを再実施する方法です。過去に別プロジェクトで利用実績のあった [NoRedInk/rspec-retry](https://github.com/NoRedInk/rspec-retry) を採用しました。

なお、alternative として [dblock/rspec-rerun](https://github.com/dblock/rspec-rerun) というものがあるようです。

# どの方法が良いのか
必ず失敗するテストを用意し、実施時間をざっくり計測してみました。

## 1. デフォルト値をそのまま利用
```
Failure/Error: find('.alert-info', text: "foobar!")

Capybara::ElementNotFound:
  Unable to find css ".alert-info" with text "foobar!"

Finished in 7.38 seconds (files took 5.33 seconds to load)
1 example, 1 failure
```

=> ロード時間 + デフォルト待ち時間（2秒）程度でテストが落ちました。

## 2. グローバル設定値を変更
```
Failure/Error: find('.alert-info', text: "foobar!")

Capybara::ElementNotFound:
  Unable to find css ".alert-info" with text "foobar!"

Finished in 9.81 seconds (files took 5.38 seconds to load)
1 example, 1 failure
```

=> ロード時間 + 待ち時間（5秒）程度でテストが落ちました。

## 3. オプション引数を利用
```
Failure/Error: find('.alert-info', text: "foobar!", wait: 5)

Capybara::ElementNotFound:
  Unable to find css ".alert-info" with text "foobar!"

Finished in 9.73 seconds (files took 5.43 seconds to load)
1 example, 1 failure
```

=> ロード時間 + 待ち時間（5秒）程度でテストが落ちました。

## 4. rspec-retry を利用
```
1st Try error in ./spec/features/sample.feature:7:
Unable to find css ".alert-info" with text "foobar!"

RSpec::Retry: 2nd try ./spec/features/sample.feature:7

2nd Try error in ./spec/features/sample.feature:7:
Unable to find css ".alert-info" with text "foobar!"

RSpec::Retry: 3rd try ./spec/features/sample.feature:7
    Step 1 -> Step 2 -> Step 3 (FAILED - 1)

Failures:

  1) Sample Step 1 -> Step 2 -> Step 3
     Failure/Error: find('.alert-info', text: "foobar!")

     Capybara::ElementNotFound:
       Unable to find css ".alert-info" with text "foobar!"

Finished in 12.03 seconds (files took 5.39 seconds to load)
1 example, 1 failure
```

=> ロード時間 + 待ち時間（2秒）* リトライ数（3回） 程度でテストが落ちました。

# 最終的にどうしたか
4番の方法を採用しました。2秒待って結果が返ってこないのであれば、それ以上待つより再実施した方が早いだろうという判断です。これでしばらく運用してみようと思います。