Capybara便利ですね。
うちではPoltergeistと組み合わせて使っています。
JSがらみのUIのテストをしていたとき、JSの動作完了を待ってくれず、テストがこけるという事があったので、JSを待つ部分の動作原理について調べてみました。
Capybara::Node::Base#syncronize
に実装がありました。
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
やらのメソッドから呼ばれています。
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_time
はsyncronize
にかかる最大秒数を設定しているので、大きな値を入れても何かバグが無い限りテストの実行時間が長くなったりはしません。
ので、うちでは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
ブロックは単に例外を投げ直します。