Seleniumを使ってWeb自動化する際に、よくある課題のひとつが「要素がまだ表示されていないのに操作しようとしてエラーになる」ことです
PythonのSeleniumでは WebDriverWait
と expected_conditions
があるため簡単に待機できますが、VBAには標準で同等の仕組みがありません
この記事では、VBAでPython風のWebDriverWaitを再現するクラスを作り、DOM上の要素が存在する・表示される・有効になるまで待機する方法を紹介します
VBAで待機処理が必要な理由
SeleniumでExcel VBAからWebサイトを操作するとき、次のような問題が発生します
- ページロードが完了していないうちに要素を取得してエラー
- JavaScriptで動的に生成されるボタンや入力欄がまだ非表示
- ボタンが表示されてもクリック可能になる前に操作しようとしてエラー
これらを解決するには「要素が存在する・表示される・有効になるまで待つ」処理が必要です
WebDriverWaitクラスでできること
今回作ったVBAクラス WebDriverWait
は、PythonのWebDriverWaitに近い操作性で以下の条件を待機できます。
メソッド | 説明 |
---|---|
UntilPresence(By) | DOM上に指定した By が存在するまで待機 |
UntilVisibleLocated(By) | 画面上に By で指定した要素が表示されるまで待機 (IsDisplayed ) |
UntilVisible(element) | 指定した WebElement が表示されるまで待機 |
UntilEnableLocated(By) | 画面上に By で指定した要素が利用可能になるまで待機 (IsDisplayed & IsEnabled ) |
UntilEnable(element) | 指定した WebElement が利用可能になるまで待機 |
ポイント:
- 成功時は
WebElement
を返す - タイムアウト時は
Nothing
を返す - Pythonのように例外処理に頼らずVBAらしい安全な設計
VBA版WebDriverWaitの実装例
以下のクラスモジュールをVBAに追加してください※クラス名は WebDriverWait
にしています

'--- クラス名: WebDriverWait ---
Option Explicit
'====================================================================================================
' PythonのWebDriverWaitの一部を再現したクラス
' ※条件に合致した場合はWebElementを返して一致しなかった場合はNothingを返す
' - UntilPresenceeLocated・DOM上に指定したelementが表示されるまで待機は発生し得ないから準備していない
' - UntilPresence・・・・・DOM上に指定したByが表示されるまで待機
' - UntilVisibleLocated・・画面上に指定したelementが表示されるまで待機(IsDisplayed)
' - UntilVisible ・・・・・画面上に指定したByが表示されるまで待機(IsDisplayed)
' - UntilEnableLocated・・画面上に指定したelementが利用できるまで待機(IsDisplayed & IsEnabled)
' - UntilEnable ・・・・・画面上に指定したByが利用できるまで待機(IsDisplayed & IsEnabled)
'====================================================================================================
Private pDriver As Selenium.WebDriver
Private pTimeout As Double
Private pInterval As Double
Public Sub Init(driver As Selenium.WebDriver, Optional timeoutSeconds As Double = 10, Optional intervalSeconds As Double = 0.5)
Set pDriver = driver
pTimeout = timeoutSeconds
pInterval = intervalSeconds
End Sub
' Presence
Public Function UntilPresence(By As Selenium.By) As Selenium.WebElement
Dim start As Double: start = Timer
Dim elem As Selenium.WebElement
Do
On Error Resume Next
Set elem = pDriver.FindElement(By)
On Error GoTo 0
If Not elem Is Nothing Then
Set UntilPresence = elem
Exit Function
End If
DoEvents
Application.wait Now + TimeSerial(0, 0, pInterval)
Loop While Timer - start < pTimeout
Set UntilPresence = Nothing
End Function
' Visibility (By指定)
Public Function UntilVisibleLocated(By As Selenium.By) As Selenium.WebElement
Dim start As Double: start = Timer
Dim elem As Selenium.WebElement
Do
On Error Resume Next
Set elem = pDriver.FindElement(By)
On Error GoTo 0
If Not elem Is Nothing Then
If elem.IsDisplayed Then
Set UntilVisibleLocated = elem
Exit Function
End If
End If
DoEvents
Application.wait Now + TimeSerial(0, 0, pInterval)
Loop While Timer - start < pTimeout
Set UntilVisibleLocated = Nothing
End Function
' Visibility (element指定)
Public Function UntilVisible(elem As Selenium.WebElement) As Selenium.WebElement
Dim start As Double: start = Timer
Do
If Not elem Is Nothing Then
If elem.IsDisplayed Then
Set UntilVisible = elem
Exit Function
End If
End If
DoEvents
Application.wait Now + TimeSerial(0, 0, pInterval)
Loop While Timer - start < pTimeout
Set UntilVisible = Nothing
End Function
' Enable (By指定)
Public Function UntilEnableLocated(By As Selenium.By) As Selenium.WebElement
Dim start As Double: start = Timer
Dim elem As Selenium.WebElement
Do
On Error Resume Next
Set elem = pDriver.FindElement(By)
On Error GoTo 0
If Not elem Is Nothing Then
If elem.IsDisplayed And elem.IsEnabled Then
Set UntilEnableLocated = elem
Exit Function
End If
End If
DoEvents
Application.wait Now + TimeSerial(0, 0, pInterval)
Loop While Timer - start < pTimeout
Set UntilEnableLocated = Nothing
End Function
' Enable (element指定)
Public Function UntilEnable(elem As Selenium.WebElement) As Selenium.WebElement
Dim start As Double: start = Timer
Do
If Not elem Is Nothing Then
If elem.IsDisplayed And elem.IsEnabled Then
Set UntilEnable = elem
Exit Function
End If
End If
DoEvents
Application.wait Now + TimeSerial(0, 0, pInterval)
Loop While Timer - start < pTimeout
Set UntilEnable = Nothing
End Function
使い方サンプル
このブログの練習用ページでテストできるようにしていますので自由にテストしてください
ポイントはInit部分で引数は下記の通りです
wait.Init driver, {最大待機秒数}, {判定間隔}
Sub test()
Dim driver As New Selenium.ChromeDriver
Dim By As New Selenium.By
driver.Get "https://javeo.jp/practice_scraping/"
Dim wait As New WebDriverWait
wait.Init driver, 10, 0.5
' presence_of_element_located(By)
Dim elemUser As Selenium.WebElement
Set elemUser = wait.UntilPresence(By.Css("#input_test"))
' visibility_of_element_located(By)
Dim elemPass As Selenium.WebElement
Set elemPass = wait.UntilVisibleLocated(By.Css("#textarea_test"))
' element_to_be_clickable(By)
Dim elemBtn As Selenium.WebElement
Set elemBtn = wait.UntilClickableLocated(By.Css("[name=""checkbox_test""]"))
elemUser.SendKeys "testuser"
elemPass.SendKeys "secret"
elemBtn.Click
End Sub
まとめ
個人的にPythonでもSeleniumを使うのでWebDriverWaitが使えない不便さを感じていました
PythonとVBAでは思想も違うので適当にいじってますが、ほぼ同じように使えてスッキリです!
コメント