fix(wdio): support v9 wdio switchFrame and switchWindow#1302
Conversation
In WDIO v9 WebDriver Classic (non-BiDi), switchFrame(null) calls the WebDriver "Switch To Frame" command with id=null which switches to the top-level frame rather than the immediate parent. This broke nested frame injection and runPartialRecursive traversal, causing timeouts and wrong selector paths in the results. Fix clientSwitchParentFrame to prefer switchToParentFrame() (available in both v8 and v9 via @wdio/protocols) which correctly targets the immediate parent frame. Also skip the devtools protocol test suite on WDIO v9+ since automationProtocol: 'devtools' was removed in v9.
Replace clientSwitchParentFrame with getWindowHandles+switchToWindow+re-traverse to avoid the BiDi race condition where switchToParentFrame synchronously resets #currentContext before the async parent lookup resolves, causing subsequent BiDi calls to run in the wrong browsing context. Also fix clientSwitchWindow to prefer switchToWindow (handle-based) over switchWindow (pattern match by title/URL/name in v8).
Two fixes for WDIO v9 WebDriver BiDi mode: 1. inject() re-entry: capture the BiDi context ID when entering each frame (returned by switchFrame), then use that string ID for re-entry after deep injection instead of the original element reference. In BiDi mode, Chrome may assign new document IDs to intermediate frame contexts after switchFrame(null) from deep nesting. An element's SharedId encodes the document ID at query time; if that ID has since changed, Chrome rejects it with "no such node - SharedId belongs to different document". Passing a context ID string instead causes WDIO to re-query fresh element references via browsingContextLocateNodes, bypassing the stale-ID issue. 2. assertFrameReady(): add document.URL !== 'about:blank' to the readiness check. In BiDi mode, script.callFunction can execute in cross-origin frames (BiDi bypasses same-origin restrictions). A lazy-loaded cross-origin iframe that hasn't fetched its content yet has readyState 'complete' on its blank document, so the old check passed and the frame was never reported as frame-tested incomplete. Classic WebDriver throws on cross-origin execution, which triggered the correct frame-tested path. Checking for about:blank replicates that behavior.
automationProtocol: 'devtools' was removed in WDIO v9. Switch the ESM export integration test to use webdriver protocol with ChromeDriver, matching the approach used in the main test suite.
There was a problem hiding this comment.
Pull request overview
Adds WebdriverIO v9 compatibility for frame/window switching by introducing wrapper helpers and updating injection/navigation logic to work across WDIO v5–v9 (including BiDi-specific behavior), while updating tests and dev dependencies accordingly.
Changes:
- Introduce
clientSwitchFrame/clientSwitchWindowhelpers and refactor frame/window navigation to use them. - Update WDIO typing (
WdioBrowser) to better model v5–v9 API differences and reduce casting. - Adjust test setup to account for WDIO v9 devtools removal and update local ESM integration test to run against a spawned ChromeDriver.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/webdriverio/src/utils.ts | Adds frame/window switch wrappers and updates client validation + script execution calls. |
| packages/webdriverio/src/index.ts | Refactors frame injection and partial recursion to use wrappers; adds BiDi context-id re-entry logic. |
| packages/webdriverio/src/types.ts | Reworks WDIO browser/element typing to cover v5–v9 API differences. |
| packages/webdriverio/test/axe-webdriverio.spec.ts | Skips devtools protocol tests on WDIO v9; adjusts typings and adds validation coverage for v9 API. |
| packages/webdriverio/test/esmTest.mjs | Spawns ChromeDriver and runs ESM integration test via webdriver protocol. |
| packages/webdriverio/package.json | Bumps devDependency webdriverio to ^9.22.0. |
| package-lock.json | Updates lockfile to reflect WDIO v9 dependency graph. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This comment was marked as duplicate.
This comment was marked as duplicate.
5 similar comments
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
|
@scottmries Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub. You can ask me to try again later by mentioning me in a new comment. If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: Sorry for the inconvenience! |
Agent-Logs-Url: https://github.com/dequelabs/axe-core-npm/sessions/04c624f6-4014-43a5-a943-9b2fe3954d1d Co-authored-by: Garbee <868301+Garbee@users.noreply.github.com>
straker
left a comment
There was a problem hiding this comment.
I can't request changes on my own pr
|
There's also a few places where the code uses |
The tests passed. So, are we saying the coverage isn't where we need it to be? |
- Add unit tests for clientSwitchWindow - Remove browser-driver-manager dependency from esmTest; use chromedriver package directly
Replace `as any` casts in clientSwitchFrame and clientSwitchWindow with `as unknown as Record<string, unknown>` to avoid unsafe any usage.
|
That's what I'm curious about. I had added the proxy comment because I ran into it as a problem, so it's interesting that we can still use it in other parts of the code. |
…when available Fall back to the chromedriver npm package when CHROMEDRIVER_TEST_PATH is not set, so the test works without browser-driver-manager installed.
|
@straker I'd assumed it was working since tests were passing, but I changed it to check for functions just in case. Tests still passing, so I think we're good. |
straker
left a comment
There was a problem hiding this comment.
LGTM (I can't approve my own pr)
Garbee
left a comment
There was a problem hiding this comment.
LGTM. We should set up a test matrix to ensure we still work on 8 and 9 going forward with any changes. But that can be done separately I feel.
## [4.11.2](v4.11.1...v4.11.2) (2026-04-14) ### Bug Fixes * Update axe-core to v4.11.3 ([#1306](#1306)) ([71c4179](71c4179)) * **wdio:** support v9 wdio switchFrame and switchWindow ([#1302](#1302)) ([4689273](4689273)), closes [#1164](#1164) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com> Co-authored-by: Jonathan Garbee <jonathan.garbee@deque.com> Co-authored-by: JustasM <59362982+JustasMonkev@users.noreply.github.com> Co-authored-by: attest-team-ci <48030122+attest-team-ci@users.noreply.github.com> Co-authored-by: Zidious <41127686+Zidious@users.noreply.github.com> Co-authored-by: API Team CI User <aciattestteamci@deque.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Garbee <868301+Garbee@users.noreply.github.com> Co-authored-by: Scott Ries <scottmries@gmail.com> Co-authored-by: Scott Ries <scott.ries@deque.com> Co-authored-by: scottmries <1245800+scottmries@users.noreply.github.com>
client as anywith'switchFrame' in client/'switchToWindow' in clientinclientSwitchFrameandclientSwitchWindowgetWindowHandle()instead ofgetWindowHandles()[0]inrunPartialRecursiveesmTest.mjsviagetFreePort()helperstdio: 'inherit'to ChromeDriverspawncall inesmTest.mjsclientSwitchFrame(v9 BiDi and v8 classic paths)WdioBrowser) to model v5–v9 API differencesas anycasts: replace withunknown as Record<string, unknown>for proxy-safe typeof checks, remove unnecessary cast onimport('node:module'), use'switchFrame' in this.clientnarrowing in index.tsFixes: #1164