Proposal: Implementing WebDriver BiDi in SeleniumVBA #183
Replies: 24 comments 52 replies
-
|
I took a look at the latest bidi version - a long list of public methods (about 40?), and it looks well written. Impressive. When I have some time, I'll give a closer look. I'm especially interested in what problems are not easy to solve with the classic WebDriver but solvable with Bidi. For example, the "wait for network idle state" can be accomplished via classic WebDriver as just executing a script. That would be easy to implement without Bidi. But on some of the other things you are doing with React/Vue, I don't know enough about nor have any experience with to judge where classic falls short. I'd like to look into that some more... Anyway, great job on this! |
Beta Was this translation helpful? Give feedback.
-
|
Some random thoughts:
|
Beta Was this translation helpful? Give feedback.
-
|
I have updated the BiDiSocketCommunicator class to introduce dynamic buffering for enhanced robustness. |
Beta Was this translation helpful? Give feedback.
-
|
@hanamichi77777, once I have some time to refactor the Select/Deselect methods, this is what I plan to do with regards to WebDriver Bidi:
That way, layering your Bidi modules on top of SeleniumVBA should be seamless. I recommend that on your GitHub project that you include source and test modules to make it easier for users include the Bidi layer in their projects. Consider renaming your project to something like "WebDriver Bidi for SeleniumVBA". If you do that, I'll include a link to your GitHub project in our ReadMe.md file. What do you think about that? More feedback on your Bidi wrapper when i have time... Thanks! |
Beta Was this translation helpful? Give feedback.
-
|
Hi @hanamichi77777, I’m impressed with your BiDi Wrapper. Thanks for building and sharing it! Now that we have this library to better handle event-driven waits and actions, I’d like to find a way to streamline the “discovery phase”: the identification of which event signatures our scripts should target to work efficiently. While good CSS/XPath locators are easily and quickly discovered (manually or) by tools like SelectorsHub or AI, because the static source of the web page is sufficient and usually small enough for the LLM context window, identifying the specific network triggers or DOM mutation patterns required for efficient automation is still a manual process for me. I would love to find, for example, a network log recorder similar to the one in DevTools but much lighter and more efficient, producing useful files smaller than 1M tokens for 10–20 seconds of recording, so that we could use AI to assist in finding the best strategy. Ideas? |
Beta Was this translation helpful? Give feedback.
-
|
@hanamichi77777, I know your proposal is meant to be a (quite successful!) proof-of-concept. But like @6DiegoDiego9, I too am interested in determining what more we can currently get from BiDi over and above what we now have with classic WebDriver plus one-way CDP. My first attempt to assess this was to take your BiDi example tests and see what it would take to emulate the same without BiDi. I've gone through most of your tests, excluding the extension, cookie, and login examples, and I seem to be able to do the same with a combination of classic (with scripting) and CDP. I've including one of these tests below. Here are some preliminary observations:
Anyway, even with the observations above, I'm very enthusiastic about the possibilities using BiDi as this develops. Public Sub test_Main01()
'test to emulate BiDi Main01 but without extension stuff
Dim driver As New WebDriver
Dim elem As WebElement
driver.StartChrome
driver.OpenBrowser
driver.NavigateTo "http://keylopment.com/faq/2357"
'the following 4 code lines emulate ExecuteSelectValueAndWaitByXPath
Set elem = driver.FindElementByXPath("//select[@name='calselect']")
elem.SelectByValue "20260301"
driver.Wait 50 '<- same static wait as in BiDi script GetJsForSelectAndWait
WaitForIdleState driver, 500 '<- same as used in Bidi script ExecuteSelectValueAndWaitByXPath
Debug.Assert driver.FindElementByXPath("//h3[@class='title-level03']").GetText Like "2026*03*"
driver.CloseBrowser
driver.Shutdown
End Sub
Public Sub WaitForIdleState(oDriver As WebDriver, Optional ByVal idleTimeout As Long = 1500, Optional ByVal maxTimeToWait As Long = 30000)
' This should probably be added to WebDriver class
Dim script As String
script = vbNullString
script = script & "function waitForIdleState(idleTimeout = 1500, maxTimeout = 30000) {" & vbCrLf
script = script & "return new Promise((resolve) => {" & vbCrLf
script = script & " let req = 0, timer = null, settled = false;" & vbCrLf
script = script & vbCrLf
script = script & " const checkInterval = 100;" & vbCrLf
script = script & " const of = window.fetch;" & vbCrLf
script = script & " const oo = XMLHttpRequest.prototype.open;" & vbCrLf
script = script & " const os = XMLHttpRequest.prototype.send;" & vbCrLf
script = script & vbCrLf
script = script & " function cleanup() {" & vbCrLf
script = script & " window.fetch = of;" & vbCrLf
script = script & " XMLHttpRequest.prototype.open = oo;" & vbCrLf
script = script & " XMLHttpRequest.prototype.send = os;" & vbCrLf
script = script & " if (timer) clearTimeout(timer);" & vbCrLf
script = script & " clearTimeout(safety);" & vbCrLf
script = script & " }" & vbCrLf
script = script & vbCrLf
script = script & " function resetTimer() {" & vbCrLf
script = script & " if (timer) clearTimeout(timer);" & vbCrLf
script = script & " }" & vbCrLf
script = script & vbCrLf
script = script & " function checkIdle() {" & vbCrLf
script = script & " resetTimer();" & vbCrLf
script = script & " if (req === 0) {" & vbCrLf
script = script & " timer = setTimeout(() => {" & vbCrLf
script = script & " if (!settled) {" & vbCrLf
script = script & " settled = true;" & vbCrLf
script = script & " cleanup();" & vbCrLf
script = script & " resolve(); // success path" & vbCrLf
script = script & " }" & vbCrLf
script = script & " }, idleTimeout);" & vbCrLf
script = script & " }" & vbCrLf
script = script & " }" & vbCrLf
script = script & vbCrLf
script = script & " window.fetch = function (...args) {" & vbCrLf
script = script & " req++;" & vbCrLf
script = script & " resetTimer();" & vbCrLf
script = script & " return of.apply(this, args).finally(() => {" & vbCrLf
script = script & " req--;" & vbCrLf
script = script & " checkIdle();" & vbCrLf
script = script & " });" & vbCrLf
script = script & " };" & vbCrLf
script = script & vbCrLf
script = script & " XMLHttpRequest.prototype.open = function (...args) {" & vbCrLf
script = script & " return oo.apply(this, args);" & vbCrLf
script = script & " };" & vbCrLf
script = script & vbCrLf
script = script & " XMLHttpRequest.prototype.send = function (...args) {" & vbCrLf
script = script & " req++;" & vbCrLf
script = script & " resetTimer();" & vbCrLf
script = script & " this.addEventListener('loadend', () => {" & vbCrLf
script = script & " req--;" & vbCrLf
script = script & " checkIdle();" & vbCrLf
script = script & " });" & vbCrLf
script = script & " return os.apply(this, args);" & vbCrLf
script = script & " };" & vbCrLf
script = script & vbCrLf
script = script & " setTimeout(checkIdle, checkInterval);" & vbCrLf
script = script & vbCrLf
script = script & " const safety = setTimeout(() => {" & vbCrLf
script = script & " if (!settled) {" & vbCrLf
script = script & " settled = true;" & vbCrLf
script = script & " cleanup();" & vbCrLf
script = script & " resolve(""Error: networkIdle timeout"");" & vbCrLf
script = script & " }" & vbCrLf
script = script & " }, maxTimeout);" & vbCrLf
script = script & "});" & vbCrLf
script = script & "}" & vbCrLf
script = script & vbCrLf
script = script & "return waitForIdleState(arguments[0], arguments[1]);" & vbCrLf
If oDriver.ExecuteScript(script, idleTimeout, maxTimeToWait) Like "Error*" Then
Err.Raise 404, , "Maximum time exceeded for loading page"
End If
End Sub |
Beta Was this translation helpful? Give feedback.
-
|
Regarding Pipe Mode (Comparison with WebDriver BiDi via WebSocket) I consulted with Gemini, and the benefits of the Pipe method are indeed significant. Theoretically, Edge and Chrome browsers can read not only CDP commands but also BiDi commands (W3C Standard) via pipes (Puppeteer actually does this, utilizing BiDi without a WebDriver). Currently, the transition from CDP to BiDi is strongly recommended in the long term, and it seems we can handle this transition simply by rewriting the commands to BiDi when that time comes. Benefits (Reasons for Selection) Overwhelming "High Speed & Low Latency" Completely "Driverless" and "Easy to Distribute" Resistant to Antivirus & Firewalls Disadvantages (Constraints to Accept) Inability to Attach to an "Already Running Browser" (Major Weakness) Complete Loss of Compatibility with Selenium Classic (For Existing Selenium Users) Increased Difficulty in Debugging |
Beta Was this translation helpful? Give feedback.
-
|
This BiDiSocketCommunicator class module incorporates specific optimizations to achieve practical performance for "asynchronous and bidirectional" WebSocket communication, despite the inherent constraints of VBA being "single-threaded and synchronous (sequential processing)."The "ingenuities (strategies to overcome VBA's limitations)" in this code can be summarized into the following five main points: 1. Pseudo-Asynchronous Processing via "Adaptive Polling" Common Challenges: Solution in this Code: 2. WebSocket Implementation via Direct WinHttp API Calls Ingenuity: 3. High-Speed "UTF-8 Binary" Processing via ADODB.Stream Common Challenges: Solution in this Code: 4. Dynamic Buffer Resizing Ingenuity: 5. Avoiding "Excel Freeze" and Protecting UI Operations Ingenuity: |
Beta Was this translation helpful? Give feedback.
-
|
The BiDiCommandWrapper class is designed to break through the limitations of traditional WebDriver (Classic/HTTP-based) by fully utilizing the capabilities of WebDriver BiDi (Bi-Directional) and CDP (Chrome DevTools Protocol). Based on the code implementation, here are 5 specific points explaining how it is superior to the traditional Classic approach. 1. Elimination of Wait Times and Acceleration via "Event-Driven" Architecture Challenge (Classic): To detect page load completion, it requires polling ("Is it done yet?") multiple times, which results in high CPU load or latency. 2. Powerful "Smart Recovery" Functionality Challenge (Classic): If a page updates during operations within a frame, the reference breaks, causing the script to stop with an error. It requires writing manual code to restart from SwitchTo.DefaultContent. 3. "Network-Level" Control via CDP Integration Challenge (Classic): Blocking image loading or specific traffic is impossible without setting up a proxy server. 4. Freeze Avoidance and Asynchronous Alert Handling Challenge (Classic): Clicking a button that triggers an alert prevents VBA from proceeding to the next line of code until the alert is closed, potentially causing a deadlock (freeze). 5. "Discovery Recorder" Designed for AI Analysis Challenge (Classic): To create automation scenarios, one had to visually inspect the browser's Developer Tools to know which APIs were called or when the DOM changed. Conclusion If the traditional Classic version is a tool that "mimics a human operating a browser by hand," this BiDi version is distinctively a tool that "connects directly to the browser's cranial nerves (DevTools) to manipulate it." However, please note that sufficient testing has not yet been conducted, so unexpected bugs may occur. A dedicated testing period is required to identify and fix these issues. |
Beta Was this translation helpful? Give feedback.
-
|
So is that AI's response to my last post? I get it that AI is very impressed with its own work! :-) But that response did not address any of my points of concern... Anyway, if I was developing a BiDi layer for SeleniumVBA, I think I would be inclined to create a wrapper that would leverage BiDi only (without CDP since we already have that with classic). I'd start with the most important functional primitives such as navigation, element locators, and common element interactions, making improvements relative to WebDriver classic where (or if) possible. Also, if we are going to continue to support Firefox in SeleniumVBA, then leaving CDP out of the BiDi wrapper would be essential, I think. |
Beta Was this translation helpful? Give feedback.
-
|
BTW, I was not proposing any code - just showing that the same can be accomplished in WebDriver classic. It looks like to me that your BiDi wrapper is mostly doing things that can already be done with current functionality. It's a valid question to ask "what can BiDi buy me now, and what will it buy me in the future as it evolves." I'm trying to discern the "now" part. It's hard for me to assess that with the way the wrapper is currently written. One possible benefit now would be the BiDi extension manager (@dornech mentioned above) - can we do something with that now that we cannot do with WebDriver classic? |
Beta Was this translation helpful? Give feedback.
-
|
Another issue that could be easend via BiDi: setting download defaults. So far you have to differentiate browser type dependent. However, I learned that even an BiDi API call exists in the Python binding, it does not mean the API is supported by every browser. So it might be the case that browser independence of code is not (yet) achievable via BiDi at least not without additional workarounds |
Beta Was this translation helpful? Give feedback.
-
|
I tried using @hanamichi77777 Main10 and Main11 subs. I don't use extensions in my automation so I might be doing something wrong - weigh my experience appropriately... Main10 (BiDi) did successfully load the extension, but I had to manually click on the extension icon, and then click on the actual Google Translate extension to activate it. Using Main11 (classic) it was a complete fail - did not see the extension icon at all. So from an automation perspective, they both failed for me, but BiDi got me one step closer... @dornech - is there an easy way to activate the extension after loading using BiDi? As far as FireFox goes, it appears to me that @hanamichi77777's current BiDi setup won't connect to geckodriver. |
Beta Was this translation helpful? Give feedback.
-
|
Well with VBA I still use the classic Webdriver - which ends up in Chrome having to manually confirm the extension installation. I navigate via chrome://extensions to check if installed and otherwise install from https://chromewebstore.google.com/detail/ + Extension-ID. Then I have to manually confirm the installation and it is activated. With Python I use the BiDi API method from the Selenium language binding and the extension is isntalled and activated. (however I made some code to overcome the trouble with BiDi for Chrome not supporting archive extension files - see link to issue raised above). |
Beta Was this translation helpful? Give feedback.
-
|
If you are unable to connect to Firefox, please specify the path using the SetBrowserBinary method. |
Beta Was this translation helpful? Give feedback.
-
|
SeleniumVBA v7.1 is now BiDi compatible, meaning it should work with @hanamichi77777's BiDi layer (or anyone else's) without having to make any changes to WebJSonConverter. I added a method to WebCapabilities class called Although not really related to BiDi, I also added some smart waits similar to what @hanamichi77777's BiDi wrapper was using - here is one example using Public Sub Main01()
'Test for waiting after selecting (without extension)
Dim driver As WebDriver
Dim elem As WebElement
Set driver = New WebDriver
driver.StartChrome
driver.OpenBrowser
driver.ActiveWindow.Maximize
driver.ImplicitMaxWait = 10000
driver.NavigateTo "http://keylopment.com/faq/2357"
Set elem = driver.FindElementByXPath("//select[@name='calselect']")
elem.SelectByValue "20260301"
driver.WaitForIdleJQuery '<- waits less than half the time as a WaitForIdleNetwork
Debug.Assert driver.FindElementByXPath("//h3[@class='title-level03']").GetText Like "2026*03*"
driver.CloseBrowser
driver.Shutdown
End Sub |
Beta Was this translation helpful? Give feedback.
-
|
Oh wait - I see you made changes to WebDriver to accommodate BiDi. Are there any other changes besides these that you made? Private m_BiDiSocket As BiDiSocketCommunicator
Private m_BiDiWrapper As BiDiCommandWrapper
Private webSocketUrl_ As String
' Description: Access point for BiDi (WebDriver Bi-Directional) features.
Public Property Get bidi() As BiDiCommandWrapper
Set bidi = m_BiDiWrapper
End Property
Public Property Get socket() As BiDiSocketCommunicator
Set socket = m_BiDiSocket
End Property
Public Property Get GetWebSocketUrl() As String
GetWebSocketUrl = webSocketUrl_
End Property
' Get Local Port
Public Property Get GetLocalPort() As String
' Reliably extracts the port number from the URL (e.g., "http://localhost:9515")
Dim parts() As String
' Split by colon and retrieve the last element
parts = Split(driverUrl_, ":")
If UBound(parts) > 0 Then
' Return the last part (e.g., 9515)
GetLocalPort = parts(UBound(parts))
Else
' Fallback in case of error (default value)
GetLocalPort = "0"
End If
End Property
'in OpenBrowser:
' ==========================================================
' BiDi (WebSocket) Auto-Connect & Setup
' ==========================================================
webSocketUrl_ = resp("capabilities")("webSocketUrl")
If webSocketUrl_ <> vbNullString Then
' 1. Create instances if they don't exist
If m_BiDiSocket Is Nothing Then Set m_BiDiSocket = New BiDiSocketCommunicator
If m_BiDiWrapper Is Nothing Then Set m_BiDiWrapper = New BiDiCommandWrapper
' 2. Attempt Connection
If m_BiDiSocket.AttemptAutoConnect(Me) Then
' 3. Wire them together
Set m_BiDiWrapper.SetDriver = Me
Set m_BiDiWrapper.SetSocket = m_BiDiSocket
End If
End If
|
Beta Was this translation helpful? Give feedback.
-
|
I want to decouple SeleniumVBA's WebDriver class from your BiDi classes so that anyone can add their own BiDi layer to SeleniumVBA. That way we are not hardwiring your BiDi stuff to our WebDriver class. So maybe it could look something like this: Dim driver As New WebDriver
Dim bidi as New BiDiCommandWrapper
Dim caps As WebCapabilities
driver.StartChrome
Set caps = driver.CreateCapabilities
caps.EnableBiDiMode 'enable from the WebDriver side
driver.OpenBrowser caps
' Enable from the BiDi side
bidi.Connect driver.GetLocalPort
'or maybe bidi.ConnectTo driver
' Now do all sorts of cool stuff with bidi object
bidi.DothisAndDoThatAndWait()In the WebDriver class, we could have the necessary read-only properties to accommodate connection, such as Does that sound ok to you? Does it make sense? Are you willing to rewrite your BiDi code to allow this way of connecting? |
Beta Was this translation helpful? Give feedback.
-
|
The verification is complete. By refactoring the driver class as follows, we can utilize BiDi while eliminating dependencies. The LocalPort and webSocketUrl_are the essential pieces of information required to establish WebSocket communication. Add the following code to the Standard Module. The following is a test file that incorporates the above code. |
Beta Was this translation helpful? Give feedback.
-
|
Thank you for the update to version 7.2! |
Beta Was this translation helpful? Give feedback.
-
|
@dornech - have you tried @hanamichi77777's Main01 extension loading example? I tested on two different unpacked extensions (Google translate and an add blocker) and managed to load them without having to manually click. Not sure why I came to the earlier conclusion that it needed a manual click. Maybe you should give it a go just to see if it works for you? |
Beta Was this translation helpful? Give feedback.
-
|
In case anyone want's to test the BiDi extension capabilities:
I have used one of @hanamichi77777's use cases below but had modify to handle Unicode on my system. This is a great example of where BiDi can come in handy, as there seems to be no other way now to load an extension at runtime. Public Sub test_BiDi_extensions()
Dim driver As New WebDriver
Dim caps As WebCapabilities
' Handle Unicode literals as my system does not allow in VBA IDE
Dim TOKYO As String: TOKYO = ChrW$(&H6771) & ChrW$(&H4EAC)
Dim SHINJUKU As String: SHINJUKU = ChrW$(&H65B0) & ChrW$(&H5BBF)
driver.StartChrome
Set caps = driver.CreateCapabilities
' Required to enable Chrome extensions in BiDi
caps.AddArguments "--remote-debugging-pipe"
caps.AddArguments "--enable-unsafe-extension-debugging"
' Enable BiDi Mode
caps.EnableBiDiMode
driver.OpenBrowser caps
' Instantiate BiDi and connect it to WebDriver
Dim bidi As New BiDiCommandWrapper: bidi.ConnectTo driver.GetWebSocketUrl
' Enable Chrome extension - modify path to your unpacked extension, such as add blocker
Dim extensionPath As String
extensionPath = Environ("UserProfile") & "\Documents\SeleniumVBA\extensions\odjhnkahfagmgepoigjbindpjnmgainl\1.4_0"
bidi.ExecuteWebExtensionInstall (extensionPath)
driver.ActiveWindow.Maximize
driver.ImplicitMaxWait = 5000
' Navigate to website
driver.NavigateTo "https://transit.yahoo.co.jp/"
' Departure: Tokyo
driver.FindElementByXPath("//input[@name='from']").SendKeys TOKYO & WebKeyboard.EnterKey
' Arrival: Shinjuku
driver.FindElementByXPath("//input[@name='to']").SendKeys SHINJUKU & WebKeyboard.EnterKey
' Date/Time: Last train
driver.FindElementByXPath("//*[@id='tsLas']").Click
' Display order: Lowest price
driver.FindElementByXPath("//select[@id='s']").SelectByValue "1"
' Click search button
driver.FindElementByXPath("//input[@id='searchModuleSubmit']").Click
' Test to see if we got there!!
Set elem = driver.FindElementByCssSelector("#rsltlst > li:nth-child(1) > dl > dt > a")
Debug.Assert elem.GetAttribute("href") = "#route01"
driver.CloseBrowser
driver.Shutdown
End Sub |
Beta Was this translation helpful? Give feedback.
-
|
Hi, |
Beta Was this translation helpful? Give feedback.
-
|
I added an English description to the wiki. |
Beta Was this translation helpful? Give feedback.




Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
In collaboration with Gemini 3, I have updated the WebDriver BiDi-supported test version, built upon the foundation of SeleniumVBA. This project pushes the boundaries of VBA to eliminate the "flakiness" of modern SPAs (React/Vue) caused by dynamic DOM updates and asynchronous communication. Crucially, the codebase is architected for AI interpretability and specifically designed for AI-driven maintenance, making it possible to fully entrust the maintenance tasks to AI.
https://qiita.com/sele_chan/items/6475a1f7ae8a21435d6c
🚀 Key Features & Technical Highlights
This wrapper goes beyond simple command execution. It implements robust logic to handle "flaky" tests common in modern web environments:
⚡ Modern SPA Support (React/Vue/Angular)
Event Triggering: Automatically dispatches input and change events with {bubbles:true}. This solves the common issue where setting .value via VBA does not trigger React state updates.
Stale Element Recovery: Includes a robust retry mechanism for Stale Elements and Realm updates.
🧠 Intelligent "Network Idle" Synchronization
Smart Waits: Implements a wait strategy similar to Playwright or Puppeteer.
In-flight Request Tracking: Hooks into window.fetch and XMLHttpRequest via injected JS to wait until network traffic drops to zero (critical for dynamic content loading).
MutationObserver: Ensures DOM stability before proceeding.
🛡️ Self-Healing Context Management
SSO/Redirect Handling: Automatically detects "Context Destroyed" or "Navigated" errors.
Auto-Recovery: Forces an update of ContextID and RealmID on the fly to resume automation without crashing, even after complex redirects.
Smart Frame Handling: Recovers iframe references dynamically if the frame reloads.
👻 Deep Shadow DOM Support
Recursive Search: Supports finding elements deep within nested Shadow roots with dynamic timeout control.
Event Penetration: Dispatches events with composed: true to ensure clicks work across Shadow DOM boundaries.
🔌 Hybrid BiDi & CDP Architecture
BiDi Core: Uses standard WebDriver BiDi for the majority of commands.
CDP Tunneling: Seamlessly tunnels CDP commands (goog:cdp.sendCommand) for features not yet fully accessible in BiDi, such as:
File Download Management (Browser.setDownloadBehavior)
Stealth Mode / User-Agent overriding
Network Blocking
🚫 Anti-Freeze Alert Handling
Executes clicks via setTimeout in JS. This prevents the VBA runtime from freezing when a modal dialog/alert opens immediately after a click.
If the code does not behave as expected, providing the following information to the AI will enable it to identify the cause and propose a solution with over 90% accuracy.
1 The Full Text of BiDiCommandWrapper Currently in Use (Shared only once at the beginning).
2 Execution Code (Understanding Intent) This reveals "which function was called with what arguments (XPath, timeout values, etc.)." Example: It helps determine logic selection errors, such as "Ah, this is a scenario where ExecuteClickAndNavigate (React-compatible) should be used instead of ExecuteClick."
3 Capture of the Error Message (Symptom Identification) By looking at the implemented custom error numbers (e.g., 515: Element not found or 520: Frame not found), one can immediately see which Guard Clause in the code was triggered.
4 Logs (Evidence Verification) Most Important Since WebDriver BiDi relies on asynchronous communication, VBA error messages alone do not reveal "what actually happened on the browser side." Viewing raw JSON responses in the logs, such as {"error": "no such node", ...} or {"result": {"type": "success", ...}}, helps determine definitively whether the issue is an incorrect XPath, a timing problem, or blockage by a Shadow DOM barrier.
Cases where HTML is occasionally required Basically, the above points are sufficient, but I may ask for "HTML around the target element (copied from F12 tools)" only in special cases like the following:
5 When "The XPath seems correct, but it cannot be found": When it is necessary to check the DOM structure itself, such as elements with dynamically changing IDs (e.g., id="input_123" → id="input_456") or visibility that is complexly controlled by CSS.
Beta Was this translation helpful? Give feedback.
All reactions