見出し画像

[SeleniumVBA]非同期処理(AJAX)も含めた待機処理について考える


はじめに

インストール不要なSeleniumVBAでは、ExcelVBAを使ってやりたいことがほぼできる機能が備わっていますが、Seleniumを使用する限りは待機処理は必ず行う必要があります。

業務で使用する場合、「driver.wait 1000」といったような固定秒数による待機は、何秒あれば大丈夫なのか推測できない上、無駄な待機時間も発生するため、やむを得ない場合以外は推奨されません。

こうした場合における待機処理について、私が実際に行っている方法について紹介していきたいと思います。

1  暗黙的待機時間の設定

OpenBrowserの後に設定します。「ImplicitMaxWait」はFindElement系の実行時に適用されます。
「PageLoadTimeout」はページロード時、「ScriptTimeout」はJavaScript実行時にタイムアウトエラーが起きてしまい延長したい時に設定します。

With driver
 
 .StartChrome 'Chromeをスタート。
 .OpenBrowser 'ブラウザを立ち上げる

 '暗黙的待機時間を設定(最大10秒)。
 .ImplicitMaxWait = 10000

 'PageLoadの待機時間の最大値を設定(最大10秒)。
 .PageLoadTimeout = 1000

 'JavaScriptの待機時間の最大値を設定(最大10秒)。
 .ScriptTimeout = 10000

End With

2 IsPresentによる待機

私のやり方ですが、Findelement系を実行したい場合は、FindElementを使わずIsPresentを利用しています。以下のコードはセレクトボックスの選択に利用しています。

Dim elmfound as WebElement
With driver
  .IsPresent(By.Xpath, "//select[@name='〇〇']/option[text()='××']",2000,,elmfound) = False Then Stop
  elmfound.Click
End With

上記コードでは、Ispresentの機能により、最大待機時間(2000ミリ秒=2秒)の設定を行いながら、見つかったらelmfoundという要素を返し、見つからなかったとStopするという、複数の処理を一度に記述できることからIspresentをよく利用しています

関数 IsPresent の構造

IsPresentを使用する場合、最大待機時間はその都度設定することになります。デフォルトの待機時間は0秒です。

3 IsAlertPresentによる待機

確認ダイアログが小窓が表示される場合は、常にIsAlertPresentにより待機処理を行ってから、SwitchToAlert.Acceptを実行しています。

With driver
 'アラート処理
  If .IsAlertPresent(2000) = False Then Stop ’最大2秒待つ
  .SwitchToAlert.Accept
End With

4 On Error GoTo ~による待機

FindElement系でない場合の待機処理のように、暗黙的待機が適用されていない処理を待機する場合、こちらを利用しています。

With driver
   Dim cnt As Long
   
  'ウインドウを切り替える(〇〇はウインドウのタイトル名)
    On Error GoTo Err_Wait
        .Windows.SwitchToByTitle ("〇〇")
    On Error GoTo 0

Err_Wait:
    ’最大1秒待機(20回リトライ)
  If cnt >= 20 Then Stop 'リトライ回数に達した場合はStop
    .Wait 50
    cnt = cnt + 1
    Resume

End With

上のコードは、ウインドウを切り替える処理が完了する前に、次の処理を行うとエラーが発生することを利用しています。エラー発生時は50ミリ秒の待機をしてからリトライして最大1秒まで繰り返す処理を行っています。

5 FindElementsによる待機

要素があるのに取得した要素数が0または不足するケースがありました。暗黙的待機の適用はされないものと考えています。
経験上、ページ遷移した直後のページ読込中に起こりやすいので、サブプロシージャを作って、指定秒数待って要素数に変化がなければOKとするようにしています。

Private Sub Sub_FindElements(driver As WebDriver, elms_title As WebElements)
'============================================
'FindElementsの要素取得が完全になるまで待つ
'指定秒数待って要素数に変化がなくなるまで続ける
'============================================
With driver
    Dim cntOld As Long, cntNew As Long
    Dim cnt As Long: cnt = 0
    Do
        cntOld = cntNew
       .Wait 2000
        Set elms_title = .FindElements(By.CssSelector, ".a-link.m-largeNoteWrapper__link.fn")
        cntNew = elms_title.Count
        cnt = cnt + 1
    Loop Until cntNew = cntOld
    Debug.Print cnt
    
End With
End Sub

6 ファイルダウンロード完了まで待機

業務システムからのファイルダウンロードでは、ファイル名は可変となると思われますので、ファイルの拡張子(xlsx等)が存在するまで繰り返す処理をしています。

'保存先フォルダをTMPATHとしている
Do While Dir(TMPATH & "\*.xlsx", vbDirectory) = ""
   .Wait 300300ミリ秒待機
Loop

【参考】ダウンロードフォルダの設定

SeleniumVBAでは、SetDownloadPrefs で設定することによりOpenBrowser時に適用ができます。

Dim driver As SeleniumVBA.WebDriver
Set driver = SeleniumVBA.New_WebDriver
'一時保存フォルダの設定
Dim TMPATH As String
TMPATH = ThisWorkbook.Path & "\〇〇〇〇"

With driver
  'Chrome起動'
  .StartChrome
  'Chromeのダウンロードフォルダの設定を変更
  Set CAPS = .CreateCapabilities
  CAPS.SetDownloadPrefs TMPATH
  '開く
  .OpenBrowser CAPS
End With

7 Ajax(非同期処理)完了まで待機

これまで私が最も悩んだ事項がこれです。
現在(2024年10月現在)でも、jQueryを用いてAJAX(非同期処理)を利用しているWEBサイトはまだまだ多いと思います。

そういったWEBサイトでAJAXが仕込まれているセレクトボックスの選択や、ボタンをクリックしたとき、AJAXの処理完了まで待ってから次の処理を行わないと、期待した結果にならないことがよくありました。

これは、SeleniumVBAだけの問題ではありませんが、正しい結果を返さないことがあるのは、業務で使用する上では致命的となりかねません。

また、期待した結果にならない場合でもエラーにならず、何事もないように振る舞うため、待機処理として「On Error GoTo~」も使えません。

では、「driver.ScriptTimeout = 2000」のように設定したら待機してれるのか、と思い実行してみましたが、待機してくれませんでした。

仕方ないので、最初は訳も分からないままwaitの固定秒数をかませて対応していましたが、根本的解決になっていませんでした。

Ajaxの処理完了後、新たな要素が表示されることが明確な場合は、既存の機能(IsPresent)を使用して要素が表示されるまで待つのがベストだと思います。

しかし、既存要素の値の更新の場合は使えないので厄介です。また、値の更新がない場合も想定すると、処理が完了したかどうかをどのように判別したらよいか分からず、袋小路に入っていました。

対応策

SeleniumVBA公式サイトのDiscussionにおいて検索したところ、幸運なことにjQueryにおいては、以下の4行の追加でAjaxの処理が完了するまで待つことができることが確認できました。このコードでは50ミリ秒毎に待ってから再度処理完了の有無を確認しています。

’AJAXが発動する要素をクリックしたとする
要素.Click

'AJAXの処理が完了するまで待機
Do Until driver.ExecuteScript("return jQuery.active == 0") And _
  driver.ExecuteScript("return document.readyState== 'complete'")
    driver.Wait 50
Loop

'その後の処理
~

上記コード適用後は、意図しない不具合に遭遇したことはないので、うまくいっているものと思います。ほんと救世主のような存在でした。

もちろんWEBサイトがjQueryを使用していることが前提のコードですので、jQuery未使用の場合はエラーになって止まります。

AJAXが使用されているかどうかは、以下のようにHTML中に記述がある、もしくはリンクされたJavaScriptのファイル名で「ajax」があるどうかで確認しています。

’【Ajax使用有無の判別】

’JavaScript
$.ajax({
 url: ‘××××’ //URLまたはディレクトリを記載
 })
 .done(function(data){ // 通信が成功したときの処理 
 })
 .fail(function(data){ // 通信が失敗したときの処理
 })
 .always(function(data){ //通信の成否にかかわらず実行する処理 
});

'JSファイルのリンク
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>

おわりに

この記事における動作確認は2025年1月5日時点です。
待機処理はこれまでに紹介した方法で対応できています。SeleniumVBAは、やりたいことのほとんどが実現できるツールだと思っています。同様のツールでSeleniumBasicがありますが、WebDriverの自動更新機能が標準で装備されるなど、それ以上の優れた機能があると感じています。

いいなと思ったら応援しよう!