【VBA】VBAでWebDriverWaitを再現したクラス

本ページはプロモーションが含まれています

Seleniumを使ってWeb自動化する際に、よくある課題のひとつが「要素がまだ表示されていないのに操作しようとしてエラーになる」ことです
PythonのSeleniumでは WebDriverWaitexpected_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では思想も違うので適当にいじってますが、ほぼ同じように使えてスッキリです!

コメント

タイトルとURLをコピーしました