PythonでSelenium_ウィンドウの切替え失敗とその対処について

はじめに

前回、Seleniumで職場のルーティンを自動化した記事を書いた
しかし実行中にエラーが発生することが何度かあったので、その原因と解決作を記録する
前回↓

atc.hateblo.jp

原因

それはウィンドウの切り替えである。

PDFの添付後、別ウィンドウがポップアップし、「OK」ボタンのクリックを要される。クリックをすると当該ウィンドウは自動で消える。これをSeleniumで動かしてみると以下の流れになる。

1.ポップアップされたウィンドウに切り換える  
2.「OK」ボタンのidに対して`.click()`若しくは`.send_keys(Keys.ENTER)`を実行    
3.ポップアップされたウィンドウが自動的に消えるので、元々開いていたウィンドウに切り換える  

上記操作をSeleniumで叶えるためには、windowhandleを知る必要がある。

windowhandleとは?

ウィンドウを識別するための情報
複数のウィンドウを操作したいとき、windowhandleを使うことでSeleniumの操作対象を切り替えることができる。

  • windowhandleを取得する方法

①. 現在操作対象であるウィンドウのwindowhandleを取得

handle = driver.current_window_handle

②. 全ウィンドウ(指定したインスタンス)のwindowhandleを取得(リスト)

handles = driver.window_handles
  • ウィンドウを切り替える方法
driver.switch_to_window(handles[2])

又、window_handlesの取得順は以下のとおりとなるらしい
index0 : 1(一番最初のウィンドウ)
index1 : 3(最新のウィンドウ)
index2 : 2(最新より1つ前のウィンドウ)
一番最初のウィンドウは0に、それ以降は新しく開いたウィンドウから割り振られていく

www.seleniumqref.com


以上がwindowhandleの概要である。
これを用いてポップアップに対してSeleniumを実行すると、体感的に30%程の確立で「OK」のクリックに失敗する。 エラーコードを見てみると、「OK」のidが見つからなかったと書いてある。しかし見つかるときもある。一体なぜなのか...原因を探してみたら次のとおりだった。

原因

window_handlesの取得順がたま〜にちぐはぐになってしまっていた
(逐次取得したwindow_handlesをprintしてみたら判明した)

index0 : 3
index1 : 1
index2 : 2

index0 : 1
index1 : 2
index2 : 3

など。順番が前述の通りにならないケースがあり、理由はわからなかった...
しかし、try - exceptを用いることでこの問題をクリアすることができた

解決策

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException


wait.until(lambda d: len(driver.window_handles) > 2)
handle = driver.window_handles
driver.switch_to_window(handle[1])

try:
    driver.find_element_by_id(b1).send_keys(Keys.ENTER)
except NoSuchElementException:
    try:
        driver.switch_to_window(handle[2])
        driver.find_element_by_id(b1).send_keys(Keys.ENTER)
    except NoSuchElementException:
        driver.switch_to_window(handle[0])
        driver.find_element_by_id(b1).send_keys(Keys.ENTER)

インスタンスを用いて出現するウィンドウは全部で3つである。
1. ポップアップのウィンドウは最新 = index1に格納されるはずなので、handle[1]にスイッチ
2. その後「OK」のidを格納している変数b1に対してENTERを実行、実行されれば問題なし
3. idb1が見つからない場合、NoSuchElementExceptionエラーが発生するためexceptで救出してあげる
なお、exceptを用いる時は以下のとおり例外クラスをimportしてあげる必要がある
from selenium.common.exceptions import [TheNameOfTheExceptionClass]

7. WebDriver API — Selenium Python Bindings 2 documentation
4. except内で更にtryしてhandle[2]のウィンドウにスイッチ
5. idb1に対してENTER、実行されれば問題なし
6. idb1が見つからない場合、NoSuchElementExceptionエラーが発生するのでexceptで救出。
7. 最後のウィンドウhandle[0]にスイッチしてidb1に対してENTERを実行
8. 以上

上記であれば全windowhandleを捜索するので漏れはない。

結果

エラーが出ることはなくなりました