
[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を使用する場合、最大待機時間はその都度設定することになります。デフォルトの待機時間は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 300 ’300ミリ秒待機
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の自動更新機能が標準で装備されるなど、それ以上の優れた機能があると感じています。