LoginSignup
34
28

More than 5 years have passed since last update.

CapybaraがJSの動作を待ってくれる仕組み

Last updated at Posted at 2014-04-03

Capybara便利ですね。
うちではPoltergeistと組み合わせて使っています。
JSがらみのUIのテストをしていたとき、JSの動作完了を待ってくれず、テストがこけるという事があったので、JSを待つ部分の動作原理について調べてみました。

Capybara::Node::Base#syncronizeに実装がありました。

lib/capybara/node/base.rb
      def synchronize(seconds=Capybara.default_wait_time)
        start_time = Time.now

        if session.synchronized
          yield
        else
          session.synchronized = true
          begin
            yield
          rescue => e
            session.raise_server_error!
            raise e unless driver.wait?
            raise e unless catch_error?(e)
            raise e if (Time.now - start_time) >= seconds
            sleep(0.05)
            raise Capybara::FrozenInTime, "time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead" if Time.now == start_time
            reload if Capybara.automatic_reload
            retry
          ensure
            session.synchronized = false
          end
        end
      end

Capybara::Node::MatchersやらCapybara::Node::Findersやらのメソッドから呼ばれています。

lib/capybara/node/finders.rb
      def find(*args)
        query = Capybara::Query.new(*args)
        synchronize(query.wait) do
          if query.match == :smart or query.match == :prefer_exact
            result = resolve_query(query, true)
            result = resolve_query(query, false) if result.size == 0 and not query.exact?
          else
            result = resolve_query(query)
          end
          if query.match == :one or query.match == :smart and result.size > 1
            raise Capybara::Ambiguous.new("Ambiguous match, found #{result.size} elements matching #{query.description}")
          end
          if result.size == 0
            raise Capybara::ElementNotFound.new("Unable to find #{query.description}")
          end
          result.first
        end.tap(&:allow_reload!)
      end

例えばCapybara::Node::Finders#findでは、ノードが見つからなかった場合にCapybara::ElementNotFoundが発生しますが、それなら見つかるまでretryすれば良いじゃない、という発想ですね。
分かりやすい。
Capybara.default_wait_timesyncronizeにかかる最大秒数を設定しているので、大きな値を入れても何かバグが無い限りテストの実行時間が長くなったりはしません。
ので、うちでは10秒に設定しています(デフォルトは2秒)

昔はwait_untilというメソッドがあったようですが、Capybara2になってから消えたようです。
Why wait_until was removed from Capybara

ちなみに、調べるきっかけになったsyncronizeしてくれない件ですが、exampleにjs: trueが付いてなかったせいでした/(^o^)\
js: trueしないとdriverがPoltergeistにならず、Capybara::Driver::Nodeが利用されます。
JSも実行されません。(ちゃんと読んでないけどおそらく#execute_scriptに実装が必要)
#syncronizeではdriver.wait?falseを返すので、rescueブロックは単に例外を投げ直します。

34
28
2

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
34
28