Skip to content

feat: support WebdriverIO 10 and keep WebdriverIO 9 support - #270

Open
dprevost-LMI wants to merge 10 commits into
browserstack:mainfrom
dprevost-LMI:wdio-v10-compliance
Open

dprevost-LMI wants to merge 10 commits into
browserstack:mainfrom
dprevost-LMI:wdio-v10-compliance

Conversation

@dprevost-LMI

@dprevost-LMI dprevost-LMI commented Oct 6, 2026 •

Copy link
Copy Markdown

What is this about?

Closes #268

This PR adds WebdriverIO 10 support to @wdio/browserstack-service. WebdriverIO 9 stays supported: the runtime and peer ranges are ^9 || ^10. The dev dependencies move to WebdriverIO 10, and CI tests both major versions.

The changes follow the WebdriverIO v10 migration guide and the wdio-v10-migration skill. The code does not check the WebdriverIO version where it can test for a feature instead.

Blocked by

WebdriverIO 10 API changes

  • Multiremote: the service detects multiremote with isMultiRemote (v10) or isMultiremote (v9), and gets each instance with getInstance(). In v10, the multiremote browser has no instance properties. Before this change, getCloudProvider() threw a TypeError for every multiremote session in the service before hook, and the BiDi detection for multiremote always returned false.
  • overwriteCommand: the third argument is { attachToElement: true }, not a boolean. v10 throws on a boolean, and v9 accepts any truthy value.
  • executeAsync() removed: the CLI accessibility scripts run through execute(). The BiDi browserstack_executor routing patches executeAsync only when the command exists (v9).
  • Type renames: RequestedMultiRemoteCapabilities.

WebdriverIO 10 behavior changes

  • Appium 3: App Automate uses Appium 1.22.0 when no version is set, and WebdriverIO 10 supports Appium 3 only. On WebdriverIO 10, App Automate capabilities that do not set a version get bstack:options.appiumVersion: '3.3.0' (Android 8+, iOS 15+). Capabilities in the legacy format get browserstack.appium_version, because WebdriverIO rejects bstack:options next to legacy keys. A version that the user sets stays. The service reads the WebdriverIO major version from @wdio/cli, because webdriverio can resolve to the v9 copy that @percy/webdriverio installs.
  • Mocha 12 failHookAffectedTests: see the dashboard note below.
  • Browsing contexts: browser.url() and browser.newWindow() return browsing contexts with their own commands, and a browser-level overwriteCommand does not reach them. The service now also uses the documented { attachToBrowsingContext: true } option, only when browser.browsingContexts exists. It is used for the browserstack_executor routing of context.execute() and for the accessibility auto-scan. See the note below.
  • Strict $: the CLI accessibility commandWrapper caught the error of the original command, logged it as Error in commandWrapper, and ran the command again. In v10, every StrictSelectorError went through this path. The command now runs once, and its error goes to the test. This problem existed before v10.

Build, CI and documentation

  • @wdio/cli peer range: ^9.0.0 || ^10.0.0 (on main: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0). The service needs WebdriverIO 9 or 10, but npm installed it on WebdriverIO 5 to 8 without an error, and it did not work at runtime. WebdriverIO 7 and 8 users have the v7 and v8 release lines.
  • Dev dependencies: webdriverio and all @wdio/* packages are 10.0.1 (@wdio/logger 10.0.0, its latest version), with @wdio/cli 10 as a dev dependency. The lockfile is regenerated.
  • Root package.json (private, not published): it overrides the webdriverio peer range of @percy/webdriverio to ^9.0.0 || ^10.0.0, so npm install works in this repository. Remove it when Percy releases WebdriverIO 10 support.
  • CI matrix: WebdriverIO 10 on Node.js 24, and WebdriverIO 9 on Node.js 22. .github/scripts/check-wdio-major.mjs fails the job when the installed WebdriverIO packages are not on the expected major version. Every job builds before it installs WebdriverIO 9, because the build also generates the gRPC client that the tests import (src/grpc/generated); the published build uses the WebdriverIO 10 types. The Test step sets WDIO_MAJOR, and the getWdioMajorVersion() test expects that major.
  • README: a compatibility table (WebdriverIO 10 needs Node.js 22.19.0 or later; WebdriverIO 9 needs Node.js 18.20.0 or later), and notes about Appium 3 and Percy.
  • Changeset: .changeset/wdio-v10-support.md (minor). I added it manually, because the changeset workflow does not run for PRs from forks.

Known limits

  • Percy web (@percy/webdriverio 3.3.4): its peer range stops at WebdriverIO 9, and it calls executeAsync(). On WebdriverIO 10, npm shows an ERESOLVE overriding peer dependency warning (not an error) and installs a second webdriverio@9 under the service for Percy. Until Percy releases WebdriverIO 10 support, Percy snapshots lose the readiness gate and cross-origin iframes. Fix in progress: feat!: support WebdriverIO 8, 9 and 10 percy/percy-webdriverio#1498, to be released as 4.0.0. After that release, the service range becomes ^4.0.0 and the root overrides entry goes away. The service code does not change: 4.0.0 accepts the browser object that the service passes.
  • Percy App (@percy/appium-app 2.1.0): it finds ignore and consider regions with driver.$(xpath) and driver.$('~id'), which throw in v10 when more than one element matches. In a session with a browserName (hybrid or mobile web), its execute('mobile: …') and execute('browserstack_executor: …') calls go through BiDi, which cannot run them. Fix in progress: feat: support WebdriverIO 8, 9 and 10 percy/percy-appium-js#619. If Percy releases it as a new major, the @percy/appium-app range changes too.
  • Engine range: engines.node stays >=18.20.0, because WebdriverIO 9 users can still run Node.js 18.20. WebdriverIO 10 itself requires Node.js 22.19.0 or later.

Notes for BrowserStack

Warning

BrowserStack: please validate AI self-heal on WebdriverIO 10.

In WebdriverIO 10, $ is strict by default (strictSelectors: true). A strict $ sends
findElements (POST /session/:id/elements), not findElement, so it can count the matches.
$$ also sends findElements.

  • Sessions on BrowserStack: the service only adds the healing extension. Healing happens on
    the BrowserStack side. Please confirm that BrowserStack heals a failed findElements, or $
    will not heal on WebdriverIO 10.
  • Sessions not on BrowserStack (local browser, other grid): ai-handler.ts overwrites
    findElement only. On WebdriverIO 10, healing and AI log data do not run for $. This PR does
    not change that. If the service also overwrites findElements, an empty $$ result (which is
    normal) also starts a heal. That decision belongs to the BrowserStack AI team.

Workaround until then: strictSelectors: false in the config, or $(selector, { strict: false }).

Note

Dashboard status change: Mocha tests after a failed hook.

On WebdriverIO 10, Mocha 12 fails the tests that a failed before or beforeEach hook skipped
(mochaOpts.failHookAffectedTests, true by default). The service now reports these tests as
failed with the Mocha message Test skipped due to failure in hook "<hook>": <error>, as
WebdriverIO does. Before, it reported them as skipped. WebdriverIO 9, afterEach failures,
and failHookAffectedTests: false keep the skipped status. Please confirm that Test Reporting
accepts this status for these tests.

Warning

BrowserStack: please validate legacy capabilities on WebdriverIO 10.

WebdriverIO 9 accepted a JSON Wire Protocol new-session response (sessionId next to value).
WebdriverIO 10 accepts only the W3C response (value.sessionId and value.capabilities), and
otherwise throws WebDriver new session response is missing a session id or capabilities.
The service still supports capabilities in the legacy format (no bstack:options, for example
browserstack.local, os_version, device). Please confirm that the hub answers these sessions
with the W3C response body.

Note

BrowserStack: please confirm accessibility scans on WebdriverIO 10 browsing contexts.

In WebdriverIO 10, browser.url() and browser.newWindow() return a browsing context with its own
commands. On WebdriverIO 10, the service now also wraps the Browser commands of commandsToWrap
that a browsing context has, and runs the scan on that context (not on the browser's current
page). Element commands were already wrapped. Note that the browser command url is navigate on
a browsing context, so a url entry does not wrap context.navigate. Please confirm that this is
the scan behavior that you want, and whether commandsToWrap should list navigate.

Tip

BrowserStack: recommended Node.js changes, not part of this PR.

The dev dependencies are now WebdriverIO 10, which requires Node.js 22.19.0 or later. Two
repository settings still allow an older version. engine-strict is off in .npmrc, so npm only
warns, and the build or the tests can then fail with errors that do not point to the cause.

  • .nvmrc is v20.11.0. Consider 24, as the WebdriverIO 10 job in ci.yml.
  • release.yml uses node-version: 22 (comment: "resolves to >= 22.14") and runs npm ci,
    npm run build and npm test before it publishes. Consider 24, as the WebdriverIO 10 job in
    ci.yml. npm 11 is installed in a separate step, so the OIDC publish is not affected.

Testing

  • Both CI jobs, run locally on Node.js 24 with the same steps as ci.yml:
    • WebdriverIO 10.0.1: npm ci, npm run build, npm run lint and npm test pass, 1446 tests.
    • WebdriverIO 9.32.0 (@wdio/logger 9.29.1): npm ci, build, install of the v9 packages, version check and npm test pass, 1446 tests.
  • CI has not run on this PR yet: the pull_request jobs of a fork PR need a maintainer approval. Please approve the run.
  • Vitest reports 3 unhandled ENOENT errors from tests/uploadLogsArchive.test.ts, also on main. This PR does not change that file, and the tests pass.
  • Not done: a real WebdriverIO 10 run against BrowserStack, because it needs credentials. Suggested checks:
    1. Desktop web session with bstack:options: service startup, session name and status, Test Reporting events.
    2. Multiremote session: no TypeError in the service before hook, and a session status for each instance.
    3. Mocha suite with a failing before hook: the affected tests show as failed on the dashboard.
    4. Accessibility on a web session: command wrapping and scans.
    5. App Automate session without appiumVersion, with bstack:options: the session starts on Appium 3.3.0.
    6. App Automate session in the legacy capability format: the session starts with browserstack.appium_version 3.3.0.
    7. Web session in the legacy capability format: the session starts (see the legacy capabilities note).
    8. Real-device mobile browser session: does BrowserStack need appiumVersion there?
    9. AI self-heal with selfHeal: true on a BrowserStack session: does $ heal?
    10. BiDi session with a second tab from browser.newWindow(): tab.execute('browserstack_executor: ...') works, and accessibility scans run on that tab.

Related Jira task/s

None. This is a community contribution.

Release (mandatory for every PR — required for the ready-for-review label)

Version bump: (required — tick exactly one)

  • minor (backwards-compatible feature)
  • patch (bug fix or other small change)

Release notes type: (optional)

  • New Feature
  • Bug Fix
  • Other Improvement

Release notes (customer-facing): (optional but encouraged)

  • Added support for WebdriverIO 10. WebdriverIO 9 stays supported.
  • On WebdriverIO 10, App Automate sessions without an Appium version now use Appium 3.3.0, because WebdriverIO 10 supports Appium 3 only.
  • On WebdriverIO 10 with Mocha, tests that a failed before or beforeEach hook skipped are now reported as failed, as WebdriverIO reports them.

Release notes (internal): (required — engineer-facing; what actually changed / why)

  • WebdriverIO 10 API migration: multiremote isMultiRemote and getInstance(), the overwriteCommand options object, execute() instead of executeAsync(), renamed types.
  • Fixed a TypeError in getCloudProvider() and a wrong BiDi detection for multiremote on v10 (instance properties were removed).
  • Default appiumVersion 3.3.0 for App Automate on v10; the WDIO major version comes from @wdio/cli.
  • Mocha 12 failHookAffectedTests: the affected tests are reported as failed (legacy path and CLI path).
  • attachToBrowsingContext overwrites for the browserstack_executor routing and the accessibility auto-scan on v10 browsing contexts.
  • The CLI accessibility commandWrapper no longer runs a failed command a second time.
  • @wdio/cli peer range is ^9.0.0 || ^10.0.0 (on main: ^5 to ^9).
  • Dev dependencies on WebdriverIO 10.0.1; CI matrix with WDIO 10 on Node.js 24 and WDIO 9 on Node.js 22, both jobs build first; @percy/webdriverio peer override in the private root package.json.

Checklist

  • Ready to review
  • Has it been tested locally?

PR Validations

Run Tests: Comment RUN_TESTS to trigger sanity tests.

🤖 Generated with Claude Code

dprevost-LMI and others added 5 commits October 6, 2026 06:58
- Detect multiremote with isMultiRemote (v10) or isMultiremote (v9), and
  get each instance with getInstance().
- Pass { attachToElement: true } to overwriteCommand, which works in v9
  and v10.
- Run the CLI accessibility scripts with execute(), because v10 removed
  executeAsync(). Patch executeAsync for BiDi executor routing only on v9.
- On WebdriverIO 10, set bstack:options.appiumVersion to 3.3.0 for App
  Automate capabilities that do not set a version. App Automate uses
  Appium 1.22.0 by default, and WebdriverIO 10 supports Appium 3 only.
  Read the major version from @wdio/cli, because webdriverio can resolve
  to the v9 copy that @percy/webdriverio installs.
- Use WebdriverIO 10 dev dependencies. Override the @percy/webdriverio
  peer range in the private root package until Percy supports v10.
- Test WebdriverIO 10 on Node.js 24 and WebdriverIO 9 on Node.js 22 in CI,
  with a check of the installed major version.
- Add a changeset and a README compatibility table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WebdriverIO 10 does not store multiremote instances as properties of the
browser object. getCloudProvider() read browser[instanceName], so it threw
a TypeError for every multiremote session, first in the service before
hook. AccessibilityHandler.isBidiSession() read the same properties and
always returned false for multiremote.

Both now use getInstance(), which works in WebdriverIO 9 and 10. The test
fixtures use the v10 object shape, without instance properties.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mocha 12 (WebdriverIO 10) fails the tests that a failed before or
beforeEach hook skipped (mochaOpts.failHookAffectedTests, true by
default). The service reported them as skipped, so the dashboard and
WebdriverIO did not agree.

When the option is not false on WebdriverIO 10, the service now reports
these tests as failed, with the Mocha 12 message "Test skipped due to
failure in hook ...", on the legacy path (InsightsHandler.afterHook) and
on the CLI path (reportSuiteFailed). WebdriverIO 9, afterEach failures
and failHookAffectedTests: false keep the skipped status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- setDefaultAppiumVersion() added bstack:options to capabilities in the
  legacy format (no x:y key). WebdriverIO rejects a capability that mixes
  extension keys with legacy keys, so the session did not start. These
  capabilities now get browserstack.appium_version.
- The CLI accessibility commandWrapper caught the error of the original
  command, logged it as its own error, and ran the command again. In
  WebdriverIO 10, every StrictSelectorError went through this path. The
  command now runs once, outside the try, and its error goes to the
  caller. A failed scan setup still logs and lets the command run.
- WebdriverIO 10 browser.url() and browser.newWindow() return browsing
  contexts with their own commands, which a browser-level
  overwriteCommand does not reach. overwriteBrowsingContextCommand() also
  overwrites them with { attachToBrowsingContext: true }, only when
  browser.browsingContexts exists (WebdriverIO 9 reads any third argument
  as "attach to elements"). It routes browserstack_executor scripts from
  context.execute(), and runs the accessibility scan before the Browser
  commands of a context, on that context (legacy and CLI paths).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Central YAML (base), Organization UI (inherited), Workspace UI (inherited)
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 3d6ea3fa-7d34-4be8-a509-2634f337e64b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

dprevost-LMI and others added 2 commits October 6, 2026 07:11
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WebdriverIO 10.0.0 is released, so ^10.0.0-0 is no longer needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread package.json
Comment on lines +33 to +35
"@percy/webdriverio": {
"webdriverio": "^9.0.0 || ^10.0.0"
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To remove when percy/percy-webdriverio#1498 is merged and released

The service needs WebdriverIO 9 or 10: webdriverio, @wdio/types,
@wdio/reporter and @wdio/logger are ^9 || ^10. The peer range still
allowed @wdio/cli 5 to 8, so npm installed the service on those versions
without an error, and it did not work at runtime. WebdriverIO 7 and 8
users have the v7 and v8 release lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dprevost-LMI and others added 2 commits October 7, 2026 13:03
The WebdriverIO 9 job did not build, so src/grpc/generated did not exist
and 12 test files failed to load. Every job now builds before it installs
another WebdriverIO major; the build still uses the WebdriverIO 10 types.

The getWdioMajorVersion test expected 10. CI now sets WDIO_MAJOR for each
job, and the test expects that major (10 for a local run).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dprevost-LMI
dprevost-LMI marked this pull request as ready for review October 7, 2026 19:52
@dprevost-LMI
dprevost-LMI requested a review from a team as a code owner October 7, 2026 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add compatibilty for WebDriverIO v10

1 participant