-
Notifications
You must be signed in to change notification settings - Fork 1
Home
(1) To enable WebSocket communication from VBA, please import the one standard module starting with "BiDi_" and the two class modules starting with "BiDi" into the SeleniumVBA file you wish to use.
(2) Add the following description to the main routine of your standard module. That is all. Introduction has become significantly easier from ver. 7.2.
Dim driver As WebDriver: Set driver = New WebDriver
With driver
' Start
.StartChrome
' Browser startup settings (for both Chrome and Edge)
Dim caps As WebCapabilities: Set caps = .CreateCapabilities
caps.EnableBiDiMode ' WebDriver BiDi becomes enabled
' Open
.OpenBrowser caps
' Perform WebSocket communication to use WebDriver BiDi
Dim bidi As New BiDiCommandWrapper
bidi.ConnectTo .GetWebSocketUrl
' Typing "bidi." will display BiDi-specific methods
bidi.
(3) Set the VBE error trapping to "Break on Unhandled Errors."
In this article, I will introduce particularly powerful and practical major methods from the VBA class BiDiCommandWrapper, which fuses the WebDriver BiDi protocol and CDP (Chrome DevTools Protocol).
| Category | Method Name | Overview (Detailed Version) |
|---|---|---|
| Navigation | ExecuteNavigateAndGetStatus |
Checks HTTP status after URL transition. An SPA synchronization engine waits until communication (Net) and rendering (DOM) stop, suppressing errors immediately after transition. |
ExecuteTraverseHistory |
Moves backward or forward through browsing history by a signed delta. After the BiDi command returns, the wrapper performs bounded URL/SPA synchronization without replaying an ambiguous state-changing traversal. | |
| Element Operation | ExecuteClickByXPath |
Scrolls into the display area, focuses, and clicks. It automatically waits for communication to stabilize after the action, enabling extremely stable operation. |
ExecuteInputValueByXPath |
Simulates input using execCommand. This ensures state updates in React, etc., solving the problem where values are not reflected. |
|
ExecuteSelectValueByXPath |
Dropdown operation. Allows selection by display text as well as Value. It forcibly triggers events after selection to ensure display changes are triggered. | |
ExecuteSetFileSelectionViaDialog |
Handles file-selection flows where clicking a visible trigger opens a native file chooser and the underlying input[type=file] may be short-lived. It captures input.fileDialogOpened, applies the selected file(s) with BiDi input.setFiles, and then performs SPA synchronization. A completion signal is strongly recommended once site-specific completion evidence is known; without one, the operation can run in ungated discovery mode so the Discovery Log can reveal suitable completion evidence. |
|
ExecuteRegisterAutoClickerByXPath |
Uses WebDriver BiDi script.addPreloadScript to station a "silent observer" inside the browser. It clicks elements as soon as they appear without waiting for VBA communication. This is useful for high-speed SPA redirects or transient pop-ups. |
|
| Information Retrieval | ExecuteGetTextByXPath |
Returns normalized textContent from the first element matching the XPath. Consecutive whitespace is collapsed and leading/trailing whitespace is removed. |
ExecuteGetAttributeByXPath |
Returns an element attribute. A missing attribute is returned as Null, while an existing empty attribute is returned as an empty string. |
|
ExecuteGetPropertyByXPath |
Returns a scalar DOM property such as value, checked, or selectedIndex. Strings, Booleans, numbers, and Null are supported. |
|
ExecuteGetOuterHtmlByXPath |
Returns the complete outerHTML of the first element matching the XPath, which is useful for diagnostics and structural inspection. |
|
| Special/Hierarchical | GetIframeContextIdByUrl |
Identifies iframe by URL. By passing the obtained ID to other methods, you can operate directly inside the frame without using SwitchTo. |
ExecuteFindWindowContextId |
Finds a popup, new tab, or window by a partial URL or title and returns its contextId. It waits internally for the target context, so an external fixed delay is normally unnecessary. |
|
ExecuteShadowClick |
Recursively searches and penetrates hidden Shadow DOM using an array of CSS selectors. This enables operation of modern sites with Web Components structures. | |
| SPA Synchronization | ArmContentSignal |
Opens a one-shot completion gate when the subtree of an existing element is rewritten. This prevents the SPA wait from ending before delayed rendering begins. |
ArmNetworkSignal |
Opens a one-shot completion gate when a matching API response arrives. This is useful when a specific response provides reliable evidence that an operation has completed. | |
ArmVisibilitySignal |
Opens a one-shot completion gate when a new or hidden element becomes visible. This is useful for result panels, dialogs, and dynamically inserted controls. | |
| Management/Optimization | AddIdleIgnoreSelector |
Excludes DOM mutations inside elements matching a CSS selector from SPA-idle detection. This is useful for clocks, animations, and other continuously updated regions that are unrelated to operation completion. |
AddIdleIgnoreNetworkPattern |
Excludes matching network requests from SPA-idle detection. Because the requests are not blocked, telemetry and high-frequency background traffic can be ignored without changing page behavior. | |
ExecuteEnableResourceBlocking |
Blocks the loading of images, ads, etc. Minimizes network load and dramatically improves execution speed. | |
ExecuteWebExtensionInstall |
Installs Chrome extensions dynamically. Allows integration of ad blockers or custom tools to extend browser functionality through the program. | |
| AI Collaboration | StartDiscoveryLog |
Records browser communication, errors, DOM changes, and SPA signal activity in detail. Outputs "AI-Ready" logs that LLMs such as Gemini or ChatGPT can use to diagnose causes and propose suitable wait conditions. |
StopAndSaveDiscoveryLog |
Drains queued events, stops Discovery Log recording, and saves the result as a UTF-8 file without a BOM. The specified output file is overwritten if it already exists. |
Navigates to the specified URL and returns success/failure (e.g., 200: Success, 500: Failure) based on the HTTP status code. The internal SPA synchronization engine, which waits until both "network communication" and "DOM changes" stop, dramatically suppresses element-not-found errors immediately after navigation.
- targetUrl (String): Destination URL.
- waitNetworkIdle (Boolean): Whether to wait until communication and DOM are completely stationary (Default: True).
- contextId (String): Used to specify a target tab or frame.
Dim status As Long
status = bidi.ExecuteNavigateAndGetStatus("https://example.com/dashboard")
If status = 200 Then
Debug.Print "Transition and SPA rendering successful"
End IfMoves backward or forward through the current browsing context's session history by calling the WebDriver BiDi browsingContext.traverseHistory command.
-
delta (Long): Number of history entries to move. Use a negative value to go back and a positive value to go forward.
0is invalid. - waitNetworkIdle (Boolean): Whether to perform SPA synchronization after the traversal (Default: True).
- minStableMs (Long): Required quiet period after network and DOM activity stop.
- maxTimeoutMs (Long): Maximum time allowed for the post-traversal synchronization.
- contextId (String): Optional target tab or frame context.
' Move back one history entry
bidi.ExecuteTraverseHistory -1
' Move forward one history entry
bidi.ExecuteTraverseHistory 1The BiDi specification allows browsingContext.traverseHistory to return before the destination is fully restored. For this reason, the wrapper follows the command with bounded URL and SPA synchronization.
History traversal is state-changing. If a transport timeout, disconnection, or context-loss error makes the result ambiguous, the wrapper does not resend the command, recover it against another context, or retarget it to the current main context. This prevents an unintended second Back or Forward operation. When the restored page has a known completion condition, verifying a destination-specific element is still recommended.
Ensures execution in the order of "Auto-scroll to view" → "Focus" → "Click". Because it automatically performs synchronous waiting until any asynchronous communication triggered by the operation ends, extremely stable continuous operation is possible.
- xpath (String): XPath to identify the target element.
- searchTimeoutMs (Long): Maximum wait time for the element to appear (milliseconds).
- waitNetworkIdle (Boolean): Whether to wait for communication updates after clicking.
' Reliable click including scroll and communication wait
bidi.ExecuteClickByXPath "//button[@id='submit-order']"By internally utilizing JavaScript's execCommand('insertText'), it generates events equivalent to a human typing on a keyboard. This solves the issue where frameworks like React or Vue do not recognize simple .value overwrites because their internal state is not updated.
' Input that ensures validation in React, etc. is triggered
bidi.ExecuteInputValueByXPath "//input[@id='username']", "vba_user"Operates a dropdown (select element). Items can be selected not only by the internal Value but also by the text visible on the screen. It automatically triggers the necessary events after selection.
' Select item by displayed text (March 2026)
bidi.ExecuteSelectValueByXPath _
"//select[@name='calselect']", _
"2026年03月", _
TrueExecuteSetFileSelectionViaDialog(triggerXPath, filePaths, [searchTimeoutMs], [fileDialogTimeoutMs], [minStableMs], [maxTimeoutMs], [contextId])
Handles file-selection flows where the HTML input[type=file] is created only after the user-facing attachment button is clicked and may exist only for a very short time. Instead of requiring VBA to locate that transient input directly, this method follows the browser's file-dialog flow.
Internally, the operation proceeds as follows:
- Resolves
triggerXPathand clicks the trigger using a trusted WebDriver BiDi pointer action. - Suppresses the Chromium native file chooser with the narrowly scoped CDP command
Page.setInterceptFileChooserDialog(enabled=true, cancel=false). - Observes the WebDriver BiDi
input.fileDialogOpenedevent. - Obtains
event.element.sharedIdfrom the event and applies the file path(s) using BiDiinput.setFiles. - Releases the native-picker interception and performs the final SPA synchronization.
Important: The name FileSelection is intentional.
input.setFileschanges the selected files of the HTML file input; it does not by itself mean that the web application's upload or subsequent processing has completed.Once a reliable application-level completion condition is known, arming
ArmNetworkSignal,ArmContentSignal, and/orArmVisibilitySignalis strongly recommended. However, an arm is deliberately not required before calling this method.With no active completion signal, the method performs an ungated idle-only synchronization. When Discovery Log recording is enabled, this first run acts as a discovery pass: its DOM and network activity can be used to identify a suitable completion signal for the next run. This follows the project's log-driven observe → arm → re-measure tuning cycle.
- triggerXPath (String): XPath of the visible button or element that opens the file chooser.
- filePaths (Variant): File path or file paths to select. When multiple files are supplied, the target file input must support multiple selection.
- searchTimeoutMs (Long): Maximum time to find the trigger element (Default: 10000 ms).
-
fileDialogTimeoutMs (Long): Maximum time to wait for
input.fileDialogOpenedafter the trigger is clicked (Default: 10000 ms). - minStableMs (Long): Required quiet period after the armed completion signal(s) have been satisfied.
- maxTimeoutMs (Long): Maximum time allowed for the final SPA synchronization.
- contextId (String): Optional target tab, popup, or iframe context. The completion signal must be armed for the same context.
Dim attachPath As String
attachPath = "C:\temp\report.pdf"
' Optional but recommended once the site's completion evidence is known.
' Replace the URL pattern with one appropriate for the target site.
bidi.ArmNetworkSignal "/api/upload"
' Click the attachment button, capture the file-dialog event,
' select the file, and then perform SPA synchronization.
bidi.ExecuteSetFileSelectionViaDialog _
"//button[@id='attach-file']", _
attachPathWhen the correct completion condition is not yet known, the method can also be run without an arm while Discovery Log recording is enabled:
bidi.StartDiscoveryLog
bidi.ExecuteSetFileSelectionViaDialog _
"//button[@id='attach-file']", _
attachPath
bidi.StopAndSaveDiscoveryLog "C:\temp\discovery_log.txt"Inspect the resulting DOM and network activity, choose a concrete completion signal, arm it, and then run the same operation again. An ungated STABLE result is diagnostic rather than proof that application-level processing completed, because both "the application finished" and "the application ignored the change" can end in a quiet state.
Use ExecuteSetFilesByXPath when the input[type=file] itself can be located reliably. Use ExecuteSetFileSelectionViaDialog when the normal user flow begins by clicking a visible attachment control and the actual file input is transient or otherwise difficult to address directly.
The trigger click and input.setFiles are state-changing operations and are not blindly replayed after an ambiguous result. If file selection succeeds but the final completion wait fails, the raised error indicates that application work may already have started, so the entire file-selection operation should not simply be retried.
Registers a persistent JavaScript-based MutationObserver through WebDriver BiDi script.addPreloadScript. The script is installed for newly created documents and frames before ordinary VBA-side element lookup begins, allowing transient controls to be handled without waiting for an additional VBA round-trip.
' Example: Handle a "Save Successful" popup that might appear after any action
bidi.ExecuteRegisterAutoClickerByXPath "//button[text()='Dismiss']"
' Proceed with standard automation without worrying about the popup timing
bidi.ExecuteClickByXPath "//input[@id='save-records']"The following read-only methods locate the first element matching the XPath and return information from that element. They do not trigger post-action SPA synchronization because they do not change browser state.
All four methods accept:
- xpath (String): XPath used to identify the first matching element.
-
searchTimeoutMs (Long): Maximum time to wait for the element (Default: 10000 ms). Specify
0for a single immediate lookup. - contextId (String): Optional target tab or iframe context.
If the target element is not found within the search timeout, an error is raised instead of returning an ambiguous empty value.
Returns normalized textContent from the element. Consecutive spaces, line breaks, tabs, and non-breaking spaces are collapsed into a single space, and leading/trailing whitespace is removed.
An existing element with no text returns an empty string.
Dim providerName As String
providerName = bidi.ExecuteGetTextByXPath( _
"//h2[@class='provider-name']")
Debug.Print providerNameReturns the result of the DOM method getAttribute(attributeName).
The return type is Variant so that the method can distinguish between:
-
Null: The attribute does not exist. -
"": The attribute exists but its value is empty. - A string value: The attribute exists and contains that value.
Dim hrefValue As Variant
hrefValue = bidi.ExecuteGetAttributeByXPath( _
"//a[@id='provider-details']", _
"href")
If IsNull(hrefValue) Then
Debug.Print "The href attribute does not exist."
Else
Debug.Print CStr(hrefValue)
End IfBecause this method reads the HTML attribute, values such as href may remain relative. Use ExecuteGetPropertyByXPath when the resolved DOM property is required.
Returns a scalar DOM property from the element. Supported result types are String, Boolean, numeric values, and Null.
This is useful for live state that may differ from the original HTML attribute, such as an input's current value, a checkbox's checked state, or a select element's selectedIndex.
Dim currentValue As Variant
Dim isChecked As Variant
currentValue = bidi.ExecuteGetPropertyByXPath( _
"//input[@id='search-box']", _
"value")
isChecked = bidi.ExecuteGetPropertyByXPath( _
"//input[@id='include-closed']", _
"checked")An undefined property, or a property whose value is an object or function, raises an error rather than returning an unsupported representation.
Returns the complete outerHTML of the element, including the element's own start tag, attributes, descendants, and end tag.
Dim cardHtml As String
cardHtml = bidi.ExecuteGetOuterHtmlByXPath( _
"//article[@data-provider-id='123']")
Debug.Print cardHtmlThis method is useful for diagnostics, saving a structural snapshot, and examining dynamically generated markup.
Penetrates and clicks through Shadow DOM (encapsulated internal structures) that cannot be reached by normal XPath searches. You pass an array of CSS selectors required to reach the target as an argument.
' Follow the Shadow Hosts in order to specify the final button
Dim path(2) As String
path(0) = "settings-ui" ' 1st host
path(1) = "settings-main" ' 2nd host
path(2) = "#confirm-button" ' Final target
bidi.ExecuteShadowClick pathIdentifies an iframe by a part of its URL and obtains an operation ID (contextId). By passing this ID to the contextId argument of any other method, you can operate directly within that frame.
Dim conID As String
conID = bidi.GetIframeContextIdByUrl("login_frame")
' Click an element inside the frame by specifying the ID
bidi.ExecuteClickByXPath "//button[@id='submit']", , , , , conIDFinds a popup, new tab, or separate top-level window and returns its contextId. The target can be identified by a partial URL or by a partial document title.
-
matchType (
BiDiMatchBy): UseMatchByUrlto search by URL orMatchByTitleto search by document title. - matchValue (String): Substring expected in the target URL or title.
- timeoutMs (Long): Maximum time to search for the target context (Default: 5000 ms).
-
waitForComplete (Boolean): When
True, the method also waits until the target document reachesdocument.readyState = "complete"(Default:True).
The method repeatedly obtains the current browsing-context tree until a matching top-level context appears. Therefore, an external fixed delay such as Sleep is normally unnecessary before calling it.
Dim popupContextId As String
' Open the popup from the current context
bidi.ExecuteClickByXPath "//button[@id='open-popup']"
' Obtain the popup Context ID by title
popupContextId = bidi.ExecuteFindWindowContextId( _
MatchByTitle, _
"Popup Test")
' Operate elements inside the popup
bidi.ExecuteInputValueByXPath _
"//input[@id='popup-input']", _
"popup-ok", _
contextId:=popupContextId
bidi.ExecuteClickByXPath _
"//button[@id='popup-button']", _
contextId:=popupContextId
' Close only the popup
bidi.ExecuteCloseContext popupContextIdUse MatchByUrl instead of MatchByTitle when the popup is more reliably identified by its URL. For SPA popups, document.readyState = "complete" does not necessarily mean that all asynchronous rendering has finished, so a target-element or SPA-specific wait may still be required after obtaining the Context ID.
Ordinary SPA synchronization waits until network activity and DOM changes become quiet. However, some sites temporarily appear quiet before delayed rendering begins. This interval is called the settle-to-render gap.
The following three methods add explicit completion evidence to the next SPA synchronization wait.
| API | Completion Evidence | Trigger Type |
|---|---|---|
ArmContentSignal(xpath) |
An existing element's subtree is rewritten | Edge |
ArmNetworkSignal(pattern) |
A matching API response arrives | Event |
ArmVisibilitySignal(xpath) |
A new or hidden element becomes visible | Level, latched |
When a signal is detected, the DOM-quiet timer is reset. The wrapper then observes a complete minStableMs quiet period after the signal. This prevents the wait from ending during the settle-to-render gap without requiring a larger fixed wait threshold.
Signals are one-shot. They are consumed when the next SPA-idle wait ends, regardless of whether the operation succeeds or times out. No separate signal timeout is added; the operation's existing maxTimeoutMs remains the single deadline.
Monitors the subtree of an existing element and opens the completion gate when that subtree is rewritten.
This is suitable for existing tables, lists, grids, or result containers whose contents are replaced after an operation.
' Evidence: the existing table body is rewritten
bidi.ArmContentSignal "//*[@id='table-body']"
' The signal applies to this click's synchronization wait
bidi.ExecuteClickByXPath yearTabXPathThe target element must already exist when the signal is armed. A missing target or invalid XPath raises an error immediately instead of silently degrading to an ungated wait.
Opens the completion gate when a matching API response is received through the WebDriver BiDi network.responseCompleted event.
The pattern uses the same notation as AddIdleIgnoreNetworkPattern. Matching is performed before Discovery Log or recorder filtering, so a filtered response can still be used as completion evidence.
' Evidence: the API response responsible for loading the results has arrived
bidi.ArmNetworkSignal "rpcids=BVAT3"
bidi.ExecuteClickByXPath searchButtonXPathThis method is useful when a specific API response provides clearer completion evidence than general network-idle detection.
Opens the completion gate when an element matching the XPath becomes visible.
This is suitable for result panels, completion messages, dialogs, buttons, and other controls that appear after an asynchronous operation.
' Evidence: the results panel becomes visible
bidi.ArmVisibilitySignal "//*[@id='search-results']"
bidi.ExecuteClickByXPath searchButtonXPathThe target must not already be visible when the signal is armed. An already-visible target or invalid XPath raises an error immediately, preventing old page state from being treated as evidence for the next operation.
Multiple signal types can be armed together. They are combined with AND semantics, meaning that all armed signals must be detected before the SPA synchronization wait can complete.
' Require both the API response and the resulting visible panel
bidi.ArmNetworkSignal "GetShoppingResults"
bidi.ArmVisibilitySignal "//*[@id='flight-results']"
bidi.ExecuteClickByXPath searchButtonXPathThis is useful when both server-side completion and browser-side rendering must be confirmed.
Clears all currently armed signals.
bidi.DisarmSignalSignal activity is recorded in the diagnostic timeline.
[SPA-SIGNAL-ARM]
[SPA-SIGNAL-HIT]
[SPA-SIGNAL-ARM-FAIL]
[SPA-SIGNAL-LOST]
[SPA-SIGNAL-DISARM]
The [SPA-WAIT-BEGIN] and [SPA-WAIT-END] lines also include signal status.
signal=net:hit,dom:miss
The quietAtHit= value shows how much apparent quiet time was discarded when the signal reset the DOM-quiet timer. These records help determine whether the selected signal is appropriate for the target SPA operation.
During SPA synchronization, continuously changing DOM regions or recurring background requests can prevent the page from being judged idle even after the main operation has completed.
The Ignore functions remove such noise from SPA-idle detection without stopping the underlying page activity.
- Ignore: The DOM mutation or network request still occurs, but it is excluded from SPA-idle detection.
- Block: The matching network request itself is stopped at the browser level.
When it is unclear whether a request is required by the page, Ignore is the safer first choice. Block should be used only for resources that have been confirmed unnecessary.
Excludes DOM mutations inside elements matching the specified CSS selector from SPA-idle detection.
This is suitable for clocks, animations, carousels, live status displays, and other regions that continue to update independently of the operation being automated.
- cssSelector (String): CSS selector identifying a DOM region to exclude from SPA-idle monitoring.
' Ignore a clock that updates continuously
bidi.AddIdleIgnoreSelector ".live-clock"
' Ignore an animation region unrelated to operation completion
bidi.AddIdleIgnoreSelector "#animation-container"This setting does not stop DOM updates. The target region continues to change normally, but those mutations no longer reset the SPA quiet timer.
Excludes network requests whose URLs contain the specified pattern from SPA-idle detection.
This is suitable for telemetry, analytics, periodic status checks, and other background traffic that is unrelated to completion of the current browser operation.
- urlPattern (String): URL substring to exclude from SPA-idle monitoring.
' Ignore telemetry traffic during SPA synchronization
bidi.AddIdleIgnoreNetworkPattern "generate_204"
bidi.AddIdleIgnoreNetworkPattern "/log?"
' Ignore a periodic heartbeat endpoint
bidi.AddIdleIgnoreNetworkPattern "/heartbeat"The matching request is not blocked. Communication between the browser and server continues normally, while only its effect on SPA-idle detection is removed.
ArmNetworkSignal matching occurs before the Ignore filter is applied. Therefore, the same response can be ignored during ordinary idle detection while still being used as explicit completion evidence for a specific operation.
' Ignore this response during ordinary SPA-idle detection
bidi.AddIdleIgnoreNetworkPattern "GetShoppingResults"
' Use the same response as explicit completion evidence for the next operation
bidi.ArmNetworkSignal "GetShoppingResults"
bidi.ExecuteClickByXPath searchButtonXPathBlocks images, ads, tracking communications, etc., at the browser level. By eliminating unnecessary communication, execution speed is dramatically improved.
' Accelerate by blocking images and ad domains
Dim blockList
blockList = Array( _
"*.png", _
"*.jpg", _
"*google-analytics.com*", _
"*doubleclick*" _
)
bidi.ExecuteEnableResourceBlocking blockListInstalls a Chrome extension from the specified path into the current browser session. This allows for dynamic integration of ad blockers, security enhancements, or specific business auxiliary tools within the automation process.
- extensionPath (String): File path to the extension.
' Settings to enable Chrome extensions
caps.AddArguments "--remote-debugging-pipe"
caps.AddArguments "--enable-unsafe-extension-debugging"
' Dynamically install a Chrome extension
bidi.ExecuteWebExtensionInstall _
Environ("LOCALAPPDATA") & _
"\Google\Chrome\User Data\Default\Extensions\" & _
"aapbdbdomjkkjkaonfhkkikfgjllcleb\2.0.16_0"Records events occurring inside the browser in an "AI-decipherable format." By feeding the saved logs to Gemini or ChatGPT, you can achieve "AI-Ready" debugging where error causes and suitable synchronization conditions can be analyzed.
The Discovery Log records network responses, DOM changes, suppressed background noise, SPA stability margins, and signal activity. This makes it easier to determine:
- Which API responses should be monitored with
ArmNetworkSignal - Which containers should be monitored with
ArmContentSignal - Which elements are suitable for
ArmVisibilitySignal - Which background communications should be ignored
- Which resources may be safely blocked
- Whether the current wait thresholds are appropriate
' Start log recording (excluding noisy image/CSS logs)
bidi.StartDiscoveryLog excludeImagesAndCss:=True
' (Execute operations...)Recording continues until StopAndSaveDiscoveryLog is called.
Stops Discovery Log recording and saves the captured timeline to the specified file. Before recording is disabled, the method drains queued events so that events already received by the communicator are not omitted from the saved log.
- filePath (String): Full path of the output log file. If the file already exists, it is overwritten.
The log is saved as UTF-8 without a byte-order mark (BOM) and includes the analysis request, configuration details, signal diagnostics, suppressed-noise summary, and the recorded timeline.
' Stop recording and save the AI-ready log
bidi.StopAndSaveDiscoveryLog "C:\temp\discovery_log.txt"The saved file can then be provided to an AI to support diagnosis and wait-condition design. In other words, the Discovery Log is not merely an execution log. It is a diagnostic tool for discovering what should be waited for when automating unknown third-party SPA sites.
Based on experiments with the following code, I confirmed that WebDriver BiDi can be enabled and events can be detected even from an already launched instance of Edge.
'=================================================
' Automatically operate a browser in a logged-in state via remote debugging.
' Start from an already launched state via a shortcut.
'=================================================
' Set the shortcut target as follows:
' "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --remote-debugging-port=9222 --user-data-dir="C:\EdgeDebugProfile"
'=================================================
Dim caps As SeleniumVBA.WebCapabilities
Set driver = SeleniumVBA.New_WebDriver
driver.StartEdge
Set caps = driver.CreateCapabilities(initializeFromSettingsFile:=False)
caps.SetDebuggerAddress "localhost:9222"
' Enable BiDi (Required to be True for this program)
caps.EnableBiDiMode
driver.OpenBrowser caps
' (Continued...)In the BiDiCommandWrapper class, we adopt a strategy of "delegating to JavaScript" via script.callFunction rather than firing repetitive, native BiDi commands.
This approach compensates for VBA’s single-threaded nature and minimizes communication overhead. Below are the three primary advantages of offloading processing to JavaScript.
The greatest challenge in VBA automation is the "Excel is not responding" state caused by synchronous communication latency.
A typical sequence—Find Element → Get Coordinates → Scroll → Click—requires at least four round-trips if executed using individual BiDi commands.
By wrapping this logic into a single JavaScript snippet and sending it via script.callFunction, the entire process is completed internally by the browser. VBA only needs to send one command and wait for a single "success" response, significantly reducing the risk of UI freezing.
The raw data returned by WebDriver BiDi often consists of deeply nested and complex JSON structures. Parsing these in VBA is CPU-intensive and leads to overly complicated code.
To avoid this, we let the JavaScript side process the data and return a simplified string, such as:
JSON.stringify({status: 'ok', value: isVisible})Consequently, the VBA side only needs to handle a simple string—often using basic regular expressions—making the overall processing remarkably lightweight.
The current WebDriver BiDi specification is still evolving and lacks high-level features that are essential for stable automation, such as "Is the element visible to the user?" (Visibility) or "Has network activity ceased?" (Network Idle).
Advanced monitoring—such as using MutationObserver to track DOM changes or hooking into fetch/XHR to monitor traffic—can only be performed by JavaScript within the browser context.
We utilize BiDi primarily as a "high-speed tunnel" to inject JavaScript, allowing the browser’s native capabilities to handle the complex logic.
By offloading logic to JavaScript, SeleniumVBA remains shielded from communication bottlenecks and complex parsing logic. This ensures a stable, responsive, and robust browser control experience within the VBA environment.
