運ゲー化していた動的UIのテストをリトライするためにrspec-retry_ex gemを導入しました

## 発生した問題

私たちが扱うプロダクト内では、ユーザーの入力値に基づき表示が変化する動的なUIが存在します。
このフロントエンドのテストはRSpecを用いて行っていました。

しかし、このような動的UIの自動テストは、何回かに1回の確率で落ちてしまい、GREENになるかどうかがある意味「運次第」となる傾向がありました。


## 問題が発生することの課題

変更していない箇所に対してテストが落ち、CIツールの画面上で「Retry」をすることは開発スピードの足を引っ張ります。
そこで、落ちやすいテストに関して自動でリトライしてくれるgemを導入しました。


## 対象となるテスト

```ruby
example '試算ができないこと' do
  visit('/try/input')
  fill_in('simulation_postcode_1', with: '100')
  fill_in('simulation_postcode_2', with: '0102')
  sleep(1)
  expect(page).to have_content('申し訳ございません。お住まいの地域はシミュレーションに対応しておりません。')

  fill_in('simulation_postcode_1', with: '100')
  fill_in('simulation_postcode_2', with: '0601')
  sleep(1)
  expect(page).to have_content('申し訳ございません。お住まいの地域はシミュレーションに対応しておりません。')
end
```

郵便番号の上3桁と下4桁を入力し、その地域がサービスに対応しないことを確認するためのテストです
郵便番号を入力した後 `sleep(1)` で1秒待ち、画面が変わってから`expect`でテストしています。
この箇所でテストが落ちやすく、`sleep(10)`にしても効果が無いことを確認しました（なぜ？）。


## rspec-retry gemとの違い

テストをリトライするgemといえば`rspec-retry` gemですが、今回導入したのは`rspec-retry_ex` gemです。
というのも、`spec-retry` gemは、scenario単位でのリトライを行うgemです。
scenario内に複数のexpectがある場合、途中のexpectでコケるとscenarioを最初からやり直すため、時間がかかる可能性があります。

一方、`rspec-retry_ex` gemはscenarioの中のexpect単位でリトライを行うため、小さな単位でのリトライができます。
これは、テストに要する時間を短縮することに繋がります。

## 機能の説明

GitHubに記載されていた例を用いて解説します。

### rspec-retry_ex gem導入前

以下のテストに対し、リトライを追加します。

```ruby
scenario "Some scenario" do
  visit "/some_page"

  fill_in "postcode", with: "1300012"
  expect(page).to have_select("ward", selected: "Sumida")
end
```

この例では`expect(page).to have_select("ward", selected: "Sumida")`が動的UIなので、これについてリトライを追加します。

### 基本形

リトライを3回するには、expectに対して`retry_ex(count: 3)`ブロックで挟みます。

```ruby
scenario "Some scenario" do
  visit "/some_page"

  fill_in "postcode", with: "1300012"
  retry_ex(count: 3) do
    expect(page).to have_select("ward", selected: "Sumida")
  end
end
```

### リトライ前に処理を追加

リトライ前に画面をリフレッシュさせ、イベントを再発火させるという狙いです。

```ruby
scenario "Some scenario" do
  visit "/some_page"

  fill_in "postcode", with: "1300012"

  # These codes are executed after the retry fails
  after_retry = -> {
    fill_in "postcode", with: "1000004"
    fill_in "postcode", with: "1300012"
  }
  retry_ex(count: 3, after_retry: after_retry) do
    expect(page).to have_select("ward", selected: "Sumida")
  end
end
```

## 導入方法

- Gemfileに記載
```
group :test do
  gem 'rspec-retry_ex'
end
```

- bundle install
```
$ bundle install
```

- `spec/rails_helper.rb`に追記
```
include RSpec::RetryEx
```


## 実装したテスト

先程紹介したように、リトライ前にイベントを再発火させるようにテストしました。

```ruby
example '試算ができないこと' do
  visit('/try/input')
  fill_in('simulation_postcode_1', with: '100')
  fill_in('simulation_postcode_2', with: '0102')
  retry_ex(count: 5, after_retry: -> {
    fill_in('simulation_postcode_1', with: '101')
    fill_in('simulation_postcode_1', with: '100')
    fill_in('simulation_postcode_2', with: '0102')
  }) do
    sleep(1)
    expect(page).to have_content('申し訳ございません。お住まいの地域はシミュレーションに対応しておりません。')
  end

  fill_in('simulation_postcode_1', with: '100')
  fill_in('simulation_postcode_2', with: '0601')
  retry_ex(count: 5, after_retry: -> {
    fill_in('simulation_postcode_1', with: '101')
    fill_in('simulation_postcode_1', with: '100')
    fill_in('simulation_postcode_2', with: '0601')
  }) do
    sleep(1)
    expect(page).to have_content('申し訳ございません。お住まいの地域はシミュレーションに対応しておりません。')
  end

end
```

## 結果
「運ゲー」化していたテストが確実に通るようになり、CIでの余計なリトライが不要になりました。


## 参考

https://tech.enechange.co.jp/entry/2019/02/22/083700
https://github.com/yuyasat/rspec-retry_ex/tree/master