フリーランチ食べたい

機械学習、Python、ソフトウェアエンジニアリング、プロダクティビティなど

SeleniumでJavascriptObjectの状態の変化を待機してから動作させる

TLDR

Custom Wait Conditionsとdriver.execute_scriptを組み合わせるとできる

Seleniumで「ある特定の動作を待つ」

最近はSPA的なサイトが増えてきたので「求めているDOMがブラウザにレンダリングされた」後に「ある特定の動作をさせたい」という気持ちになるときは多いのではないでしょうか。 そういう場合のために、Seleniumには、「待機する」用のClassが用意されています。例えば WebDriverWaitexpected_conditions です。 公式ドキュメントの例ですが、下記のように書くことができます。

5. Waits — Selenium Python Bindings 2 documentation

下記の例の場合、「idがmyDynamicElementの要素が出現するまで10秒ごと待機する」コードになります。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
try:
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "myDynamicElement"))
    )
finally:
    driver.quit()

様々な条件で待つ

presence_of_element_located 以外にも下記のようにメソッドが用意されており、様々な条件で待機することができます。

* title_is
* title_contains
* presence_of_element_located
* visibility_of_element_located
* visibility_of
* presence_of_all_elements_located
* text_to_be_present_in_element
* text_to_be_present_in_element_value
* frame_to_be_available_and_switch_to_it
* invisibility_of_element_located
* element_to_be_clickable
* staleness_of
* element_to_be_selected
* element_located_to_be_selected
* element_selection_state_to_be
* element_located_selection_state_to_be
* alert_is_present

JS Objectをチェックしたい

ただ問題としては全てHTMLの条件をチェックするということです。 待機する要素が多数に渡る場合は全ての要素をチェックするのはかなりしんどいので「一連の動作が終わったあとに、あるJS Objectの要素が書き換わる」ことをチェックしたいケースは少なからずあるのではないでしょうか。そんな場合は上記のメソッドは使えません。

Custom Wait Conditions

そんなときに使えるのがCustom Wait Conditionsで自分で特定の条件を書くことができます。 公式のも例がありますが、最もシンプルな例ではないので、もっとシンプルに書くと第二引数に driver を受け取る __call__ さえあればOKです。 __call__ は「ある特定の条件を満たしていなかったら False をそれ以外は受け取りたい要素を返す」ように実装します。

class CustomCondition:
  def __call__(self, driver):
    some_value = driver.some_method()
    if some_value == “some_condition":
        return some_value
    else:
        return False

wait = WebDriverWait(driver, 1)
some_condition = wait.until(CustomCondition())

この例だと「1秒ごとに状況をチェックしにいって、Falseでなかったら終了(値が帰ってくる)」コードになります。

driver.execute_script でJS Objectの状況取得

あとはこの条件にJS Objectの状況を入れられれば任務完了になります。SeleniumからJS Objectを取得するには driver.execute_script メソッドを使えば良いです。

7. WebDriver API — Selenium Python Bindings 2 documentation

例えばグローバルに以下のようなオブジェクトがあった時に

foo = {bar: ‘hoge'}

下記のようにして値を取得することが出来ます。

value = driver.driver.execute_script(‘return window.foo.bar’)
print(value) # => hoge

組み合わせる

これをCustom Wait Conditionsと組み合わせるとこのようになります。

class CustomCondition:
  def __call__(self, driver):
    some_value = driver.driver.execute_script(‘return window.foo.bar’)
    if some_value == “hoge":
        return some_value
    else:
        return False

wait = WebDriverWait(driver, 1)
some_value = wait.until(CustomCondition())

これで「 foo.bar == “hoge” になるまで動作を待機する」コードが書けました。めでたしめでたし。