CucumberのTimeout::Errorでリトライするようにした

* Cucumberを使ってるとたまにTimeout::Error、ありますよね
    * 特別な処理をしてるわけではなく、visitするだけでもTimeoutとか
    * 再現性もよくわからず厄介です
    * 例えばこんなエラー

```
    Timeout::Error (Timeout::Error)
      /home/hoge/.rvm/rubies/ruby-1.9.3-p551/lib/ruby/1.9.1/net/protocol.rb:146:in `rescue in rbuf_fill'
      /home/hoge/.rvm/rubies/ruby-1.9.3-p551/lib/ruby/1.9.1/net/protocol.rb:140:in `rbuf_fill'
      /home/hoge/.rvm/rubies/ruby-1.9.3-p551/lib/ruby/1.9.1/net/protocol.rb:122:in `readuntil'
      /home/hoge/.rvm/rubies/ruby-1.9.3-p551/lib/ruby/1.9.1/net/protocol.rb:132:in `readline'
      /home/hoge/.rvm/rubies/ruby-1.9.3-p551/lib/ruby/1.9.1/net/http.rb:2563:in `read_status_line'
      /home/hoge/.rvm/rubies/ruby-1.9.3-p551/lib/ruby/1.9.1/net/http.rb:2552:in `read_new'
      /home/hoge/.rvm/rubies/ruby-1.9.3-p551/lib/ruby/1.9.1/net/http.rb:1320:in `block in transport_request'
      /home/hoge/.rvm/rubies/ruby-1.9.3-p551/lib/ruby/1.9.1/net/http.rb:1317:in `catch'
      /home/hoge/.rvm/rubies/ruby-1.9.3-p551/lib/ruby/1.9.1/net/http.rb:1317:in `transport_request'
      /home/hoge/.rvm/rubies/ruby-1.9.3-p551/lib/ruby/1.9.1/net/http.rb:1294:in `request'
      /home/hoge/.rvm/rubies/ruby-1.9.3-p551/lib/ruby/1.9.1/net/http.rb:1287:in `block in request'
      /home/hoge/.rvm/rubies/ruby-1.9.3-p551/lib/ruby/1.9.1/net/http.rb:746:in `start'
      /home/hoge/.rvm/rubies/ruby-1.9.3-p551/lib/ruby/1.9.1/net/http.rb:1285:in `request'
```

## 対処方法を調べてみた
* 結論から書いてしまうと、解決法はないようです
* でもうまくやればTimeout::Errorのときだけリトライすることはできそうです

#### timeout伸ばしとけー
http://stackoverflow.com/questions/12952132/timeout-on-javascript-form-only-running-cucumber-on-jenkins

```rb
client = Selenium::WebDriver::Remote::Http::Default.new
client.timeout = 300 # seconds – default is 60
$browser = Watir::Browser.new :firefox, :http_client => client
```

* 単純に処理が長引いてるなら効くのですがフリーズしてたりすると効果なし
* さらに伸ばした分テストも伸びるので場合によっては非常に辛い

#### 失敗したシナリオをリトライ、はできない
http://stackoverflow.com/questions/14019425/retry-on-timeout

```rb
Around do |scenario, block|
  for i in 1..5
    begin
      block.call
      break
    rescue Timeout::Error
      next
    end
  end
end
```

* これができればよかったのですが、ここにも書かれてるようにAroundのバグで複数回ブロックを実行することが出来ないのでNG
    * https://groups.google.com/forum/?fromgroups=#!topic/cukes/YsbX8QdDscg
    * 少し試してみた感じだと 表 があるステップの実行がうまくいかないようでした
    * リトライできるシナリオもあるのでテストの書き方に気をつける(表を使わないなど)すればこれでも良いかも

## もう少し抗ってみた
* Timeout::Errorは不可避受け入れるしかない、ならばリトライだ という方針でやってみました

### 結果
* retry.rb
    * features/support/下などに配置

```rb:retry.rb
module Cucumber
  module RbSupport
    module RbWorld 
      NUM_OF_TRIALS = 3
      def step_with_retry(name, multiline_argument=nil)
        trying_count = 0
        loop do 
          begin   
            trying_count += 1
            step_without_retry(name, multiline_argument)
            break   
          rescue Timeout::Error => e
            if NUM_OF_TRIALS > trying_count
              $stderr.puts "[RETRY][Timeout::Error] #{name} : #{trying_count}"
              next    
            else    
              raise e 
            end     
          end     
        end     
      end     

      alias step_without_retry step
      alias step step_with_retry
    end
  end
end
```

* 上記のコードを読み込むことで、ステップ実行時にTimeout::Errorが起きてもそのステップを最大２回リトライするようになります
    * ３回目のTimeout::Errorはraiseするのでそこでシナリオは失敗となります
    * http://stackoverflow.com/questions/14019425/retry-on-timeout はシナリオ単位でリトライしていたのに対し、こちらではステップ単位でのリトライです
* このコードを使用する場合はtimeoutを短めに設定すると良いのではないかと思われます
    * これは好みですが、defaultはNet::HTTPの60sなので私は30sにしてみてます
    * 30sで読み込みが終わらないとかもう異常だろって考えです

```
 client.timeout = 30
```

* いやいや、これまずいよって場合は是非コメントなどで教えてやってください！

## おまけ
* timeoutが起きないように出来ないかと以下のことを試してみましたが効果は確認できませんでした
    * selenium-webdriverの更新(2.38.0->2.44.0)
    * firefoxの更新(31.0->35.0)