LoginSignup
11
8

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-12-26

CapybaraPhantomJS (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 を使ったところ問題が解決した

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)

実際の処理は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

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

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)

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

方法3. rspec-retry で特定のテストを再実施する

上記のオプション引数を渡す方法を使っていたのですが、それでも数回に一回失敗していました。徐々に閾値を上げていったのですが、 wait: 30 でもまだ失敗するので別の方法を模索しました。

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

なお、alternative として 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秒待って結果が返ってこないのであれば、それ以上待つより再実施した方が早いだろうという判断です。これでしばらく運用してみようと思います。

11
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8