Le Selenium avec P04:implicit wait とexplicit wait
【0】はじめに
今回はWebアプリケーションのレスポンス(描画速度)にあわせたSeleniumの動作タイミングの調整について簡単にまとめておく。
サーバ負荷やネットワーク遅延によるウェブアプリの遅延の他、ウェブアプリの動的なレンダリングによって、「ブラウザ上に描画される前にseleniumが動いてしまい、elementが見つからずにエラーとなる」ということもありえてしまう。
■例1:Selenium側が早すぎる例:サンプルはOWASP Juice Shop
from selenium import webdriver
import chromedriver_binary # pipでいれたのでこれが必要
#chrome起動時のオプション設定
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-logging"]) # bluetoothデバイス系の警告OFF
driver = webdriver.Chrome(options=options)
driver.get('http://localhost:3000/#/')
# 最初のオーバーレイ上のボタンをクリック
overlay = driver.find_element_by_xpath('//*[@id="mat-dialog-0"]/app-welcome-banner/div/div[2]/button[2]/span[1]/mat-icon')
overlay.click()
# メニューボタンをクリック
menu_button = driver.find_element_by_xpath('/html/body/app-root/div/mat-sidenav-container/mat-sidenav-content/app-navbar/mat-toolbar/mat-toolbar-row/button[1]/span[1]/mat-icon')
menu_button.click()
# Loginをクリック
login_button = driver.find_element_by_xpath('/html/body/app-root/div/mat-sidenav-container/mat-sidenav/div/sidenav/mat-nav-list/div/a/div/span')
login_button.click()
#driver.quit()
そこで、Seleniumにはブラウザのレスポンスにあわせて、動作タイミングを調整するために「implicit wait」と「explicit wait」という2種類のウェイトの仕組みを持っている。
■time.sleep()との違い
ソースコード上に「time.sleep()」を適当に配置して待ち時間をつくる、という方法はちょっとした処理であれば構わない。しかし、Webページの読み込みのための待機時間を作っているわけではないことに注意しよう。
例えばソースコード上にtime.sleep(10)を配置した時、10秒経過前にブラウザ上の描画が完了していても10秒間は待つことになってしまう。
逆に、サーバ負荷等でブラウザの描画完了まで10秒以上かかったら、elementを見つけられずエラーとなってしまう。
一方Seleniumの「implicit wait」や「explicit wait」はWebページの読み込みに合わせたウェイトをする。つまり、目的のelementが表示されるまでウェイトがかかり、表示されたら次に処理が進む、というより最適なウェイトがかかる。
【1】implicit wait
「implicit wait」は「使用するWebDriverの処理全体に設定するウェイト」。具体的には、「読み込んだWebページで要素がすぐ利用できない(見つからない)場合、implicit waitで設定した時間だけポーリング(再検索)を繰り返す」。
■例2:implicit wait10秒を設定
from selenium import webdriver
import chromedriver_binary # pipでいれたのでこれが必要
#chrome起動時のオプション設定
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-logging"]) # bluetoothデバイス系の警告OFF
driver = webdriver.Chrome(options=options)
# implicit wait 10秒
driver.implicitly_wait(10)
driver.get('http://localhost:3000/#/')
overlay = driver.find_element_by_xpath('//*[@id="mat-dialog-0"]/app-welcome-banner/div/div[2]/button[2]/span[1]/mat-icon')
overlay.click()
menu_button = driver.find_element_by_xpath('/html/body/app-root/div/mat-sidenav-container/mat-sidenav-content/app-navbar/mat-toolbar/mat-toolbar-row/button[1]/span[1]/mat-icon')
menu_button.click()
login_button = driver.find_element_by_xpath('/html/body/app-root/div/mat-sidenav-container/mat-sidenav/div/sidenav/mat-nav-list/div/a/div/span')
login_button.click()
#driver.quit()
これにより、要素が利用できない(見つからない)場合でも、最大10秒要素検索をリトライしてくれるようになる。
【2】explicit wait
「implicit wait」はWebDriver全体に設定するウェイト処理。これに対し「explicit wait」はより細かく制御でき、「要素ごとにウェイトタイミングを設定できる」。
例えば、OWASP Juice Shopのメニュー押下後、「Loginが配置されるまでウェイトをかける」、といった制御ができる。
■explicit waitのかけ方
「explicit wait」を設定するには、「WebDriverWait」と「expected_conditions」を組み合わせて使う。
■例3:Loginが表示されるまでウェイト(タイムアウトは10秒)
from selenium import webdriver
import chromedriver_binary
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By
... ...
driver = webdriver.Chrome()
... ...
login_button2 = WebDriverWait(driver,10).until(
expected_conditions.visibility_of_element_located(
(
By.XPATH,
'/html/body/app-root/div/mat-sidenav-container/mat-sidenav/div/sidenav/mat-nav-list/div/a/div/span'
)
)
)
#要素をクリックする
login_button2.click()
■全体コード
from selenium import webdriver
import chromedriver_binary # pipでいれたのでこれが必要
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By
#chrome起動時のオプション設定
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-logging"]) # bluetoothデバイス系の警告OFF
driver = webdriver.Chrome(options=options)
#implicit wait
#driver.implicitly_wait(10)
driver.get('http://localhost:3000/#/')
overlay_button = driver.find_element_by_xpath('//*[@id="mat-dialog-0"]/app-welcome-banner/div/div[2]/button[2]/span[1]/mat-icon')
overlay_button.click()
menu_button = driver.find_element_by_xpath('/html/body/app-root/div/mat-sidenav-container/mat-sidenav-content/app-navbar/mat-toolbar/mat-toolbar-row/button[1]/span[1]/mat-icon')
menu_button.click()
# 「Login」にexplicit waitを設定
login_button = WebDriverWait(driver,10).until(
expected_conditions.visibility_of_element_located(
(
By.XPATH,
'/html/body/app-root/div/mat-sidenav-container/mat-sidenav/div/sidenav/mat-nav-list/div/a/div/span'
)
)
)
login_button.click()
#こっちだとウェイトがないためSeleniumが処理が先に動いてエラー
#login_button = driver.find_element_by_xpath('/html/body/app-root/div/mat-sidenav-container/mat-sidenav/div/sidenav/mat-nav-list/div/a/div/span')
#login_button.click()
#driver.quit()
【補足】
「implicit wait」と「explicit wait」を混ぜて使わないほうがよい。詳細は省くが、簡単に言うと
・implicit wait ⇒ ブラウザの動作上の範囲
・explicit wait ⇒ プログラム(ソースコード)上の範囲
で制御されるものという違いがある。
そのため、例えば、「全体のウェイト設定をimplicit waitでいれておき、さらに細かい制御をexplicit wait側で行ってウェブアプリをテストする」、というような混在設定をしても、タイミングがうまく合わず想定通りには動かない、といったことにおちいることがある。
プログラムの動作テストをするなら「implicit waitよりもexplicit wait」を使っておくほうがよい。