目的
社内サイトの入力が大変なので、seleniumを使って自動化する。その中で、
以下のようなselect要素を選択したい時に、はまったのでそのメモ。
<select onchange="return (DropDownList.OnChange(this, event));"
onfocus="return (DropDownList.OnFocus(this, event));"
onblur="return (DropDownList.OnBlur(this, event));"
id="aaa"
scriptclass="DropDownList"
class="bbb"
wrapped="true"
direction="ltr"
viewdatanode="6"
formid="ccc"
originalid="ddd"
tabindex="0"
title=""
style="direction:ltr">
<option value="" selected=""></option>
<option value="1">Alice</option>
<option value="2">Bill</option>
<option value="3">Chris</option>
<option value="4">Dan</option>
</select>
JavaScriptを使って要素を埋めるような形?そもそもoptionが最初から埋まっているので、何をJavaScriptでやっているのか、そのあたりはよくわかりません。
環境
OS: Windows10
Python: 3.6.4
selenium: 3.4.3
driver: ChromeDriver 2.40.565498
importは以下。
from selenium import webdriver
from selenium.webdriver.support import ui
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common import exceptions
import time
試み
最初にやったのは、単純に通常ブラウザで自分が操作するように、フォーカスしてoptionの頭文字をうつこと。
select_dropdown = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID,'aaa')))
select_dropdown.send_keys('A')
これは以下のエラーで失敗する。
selenium.common.exceptions.WebDriverException: Message: unknown error: cannot focus element
少し調べたところ、select要素を選ぶにはSelectというクラスを使うらしいので、トライ。
select_dropdown = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID,'aaa')))
select = Select(select_dropdown)
select.select_by_index(1)
これは以下のエラーで失敗。
raise NoSuchElementException("Could not locate element with index %d" % index)
# selenium.common.exceptions.NoSuchElementException: Message: Could not locate element with index 1
調べると、要素をクリックしないとoptionが選択できないケースがあるようなので試す。
select_dropdown = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID,'aaa')))
select_dropdown.click()
select_dropdown = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID,'aaa')))
select = Select(select_dropdown)
select.select_by_index(1)
これはPyCharmのデバッガで1行ずつ実行すると普通に成功するが、上からrunすると失敗する。ただし、失敗した後に要素を選択することができるので、try/exceptで無理やり回避してみる。あと、ExpectedCondition(EC)というのもちゃんと理解していなかったが、このタイミングで調べて一番適切に思われるものに変えた。ちなみに、try/exceptなしでECだけ変更してもダメだった。
time.sleep(5)
select_dropdown = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID,'aaa')))
select_dropdown.click()
try:
select = Select(select_dropdown)
select.select_by_index(1)
except exceptions.StaleElementReferenceException:
select_dropdown = WebDriverWait(driver, 10).until(EC.presence_of_element_located(
(By.ID, 'aaa')))
select = Select(select_dropdown)
select.select_by_index(1)
except:
pass
これはうまくいった。
ただし、このサイトは同じような入力個所が複数あり、連続で埋めてくると要素を取得できないケースがあったので、先頭にsleepを少し入れている。
まとめ
いったんクリックしたうえで、selectインスタンス化に一度失敗させてから、リトライするとうまくいった。おそらく詳しい人から見るともっとやり方があるのだろうけど、現時点ではこれでうまくいっているのでよしとする。
sleepの部分もよくないとは思うのだが、具体的に何を待てばよいのか不明なので、このまま使っている。