seleniumで要素を待つ時にsleepを使うのはオススメしない

no such element: Unable to locate elementとかElement is not clickable at pointとかを回避するためのwait

ruby + selenium webdriverを使っていれば誰もが見たことあるこれらのエラー。
動作が速すぎて要素が現れる前に処理が実行されてしまいこのエラーが出ちゃうことが多いですよね。

秒数を指定して動作を待つsleep

sleep 10

このように書けば10秒待ってから次の処理を行うというコマンドです。

でも本当に10秒待てば次の要素は必ず現れるのでしょうか?
ネットワークや実行する環境によっては1秒で現れてくれるかもしれないし、12秒待っても現れてくれないかもしれません。
前者の場合は1秒で現れてくれるのだから9秒無駄になってしまい、sleepを書く度にその無駄な時間が積み重なっていきます。
後者の場合はもちろんエラーで動作が止まってしまいますね。

要素が現れるまでwaitし、現れたら実行する

sleepが危なっかしくて、しかも時間を無駄にしてしまうかもしれないということをわかっていただいた上でオススメするのがこれから紹介するコードです。
要素が現れたら次の処理に移ってくれるので、無駄な時間も省けるし、sleepのように設定した時間を超えた場合でも待っていてくれます。

waitクラスを使ってみる

要素を待つ最長の時間を設定できるのがwaitクラスです。
インストールやrequireなどは特に必要ありません。
よく使うのは以下のような感じ。

require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome

# :timeoutオプションは秒数を指定している。この場合は100秒
wait = Selenium::WebDriver::Wait.new(:timeout => 100) 

# untilメソッドは文字通り「~するまで」を意味する
wait.until {driver.find_element(:id, 'submit').displayed?}

# この後に処理を書けばその要素が現れてからその処理を行ってくれる
driver.find_element(:id, 'submit').click

timeoutの時間を設定してくれているので、この場合は100秒を超えた場合はタイムアウトのエラーが返ります。
1秒後に要素が現れてくれた場合はtrueとなり、次のコードを実行します。

waitクラスはこちらに公式ドキュメントがあるので参考にしてみてください。

Class: Selenium::WebDriver::Wait — Documentation for selenium-webdriver (3.4.3)

waitではうまくいかない時はsizeを使ってみる

waitはとても便利なのですが、理由はわからないけどこれではtrueとなった後に要素が見つからないというエラーとなってしまう場合があります(理由がわかる方コメントお願いします)。

そんな時は、driverに要素が存在するかを調べてもらっています。

loop do
  if driver.find_elements(:id, 'submit').size > 0
    break
  else
    sleep 0.5
  end
end

driver.find_element(:id, 'submit').click

find_elementsになっていることに注意してください。

このように書くと要素が1個以上存在した場合はloopを抜けてくれ、
まだ見つからない場合は0.5秒待ってからまた要素を探してくれます。
これ、意外と使えます。

ただ、これだと要素が見つからない場合は永遠にloopし続けるので、
制限を設けたい場合は、ループ処理をforとかに変えてやってみれば良いと思います。

とにかくsleepだけはコードに入れないようにしている

sleepで待つ時間は予想でしかありません。
無駄な時間を使うし、実行環境の関係で待つ時間が足りなくなりかもしれません。

要素を待つ場合はsleepを使うのはとにかくオススメしません。
確実に何秒待てば良いという場合以外はsleepを使うのはやめましょう。

自分のブログの紹介

ruby + selenium webdriverについてを中心にブログをやっていますので、そちらもよろしくお願い致します。
katsulog