Skip to content

feat: add the Selenium bridge for RemoteWebDriver and BiDi - #2463

Merged
mykola-mokhnach merged 9 commits into
masterfrom
stage6
Oct 5, 2026
Merged

mykola-mokhnach merged 9 commits into
masterfrom
stage6

Conversation

@mykola-mokhnach

@mykola-mokhnach mykola-mokhnach commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Stage 5 of cutting java-client's dependency on Selenium down to selenium-api. #2462 removed selenium-remote-driver and BiDi from the core, so AppiumDriver is no longer a RemoteWebDriver and does not implement HasBiDi. This PR adds an optional artifact, io.appium:java-client-selenium-bridge, for the code that still needs them, such as the Selenium Augmenter or the BiDi modules (LogInspector, etc.). The core is unchanged apart from one getter.

What changed

  • Build: multi-project build. The core stays at the root, and the new selenium-bridge subproject publishes java-client-selenium-bridge with the version of the core. It depends on the core and on selenium-remote-driver within the same [4.50.0, 5.0) range as selenium-api, so both resolve to one Selenium version. Repositories and the common POM data are shared.
  • SeleniumBridge.asRemoteWebDriver(driver) returns a Selenium RemoteWebDriver that works in the session of the Appium driver. Commands go through the command executor of the Appium driver, so both drivers share the session and the HTTP client. quit() quits the session. An overload takes a Selenium HttpClient.Factory for the BiDi WebSocket.
  • BiDi: the bridge is built with the public RemoteWebDriver(CommandExecutor, Capabilities, HttpClient.Factory, ClientConfig) constructor. The executor answers the new session command with the existing Appium session, so Selenium takes it over and creates the BiDi connection itself from the webSocketUrl capability, with the timeouts, proxy, credentials and SSL context of the AppiumClientConfig. Nothing deprecated is overridden or called (maybeGetBiDi, Response#setStatus), and the module compiles with -Xlint:deprecation,removal.
  • Core: AppiumCommandExecutor#getClientConfig().
  • Docs: new docs/selenium-bridge.md, linked from the README and the migration guide.

How it was verified

  • Unit tests of the bridge run against a fake Appium HTTP server and a fake BiDi WebSocket server: shared-session commands, error mapping, quit(), Augmenter, the BiDi connection, LogInspector events and the missing-capability case.
  • AndroidBiDiTest and IOSBiDiTest, removed in feat!: replace selenium-remote-driver, selenium-http and selenium-json #2462, are restored as e2e tests of the bridge, written with the Selenium LogInspector module. They run with the existing e2eAndroidTest and e2eIosTest tasks, so CI needs no change.
  • Release pipeline: ./gradlew publish stages both modules, and a JReleaser dry run (jreleaserDeploy) with a throwaway key verified the POMs of both, signed both, and built a single Maven Central bundle containing the core and the bridge.
  • ./gradlew build: the same 3 environment-only failures as on master (TimeoutTest, StorageTest, DesktopBrowserCompatibilityTest). Checkstyle and Javadoc are clean for both projects.

Known issues / follow-ups

  • The bridge depends on RemoteWebDriver and the BiDi classes of Selenium, so the Selenium snapshot job may fail first when Selenium changes them. That is the reason it is a separate artifact.
  • BiDi connects when the bridge is created, if the session has the webSocketUrl capability.
  • The BiDi e2e tests run for the first time in this PR's CI.
  • Next: an OpenRewrite recipe to migrate clients from v10, and the README compatibility matrix and version bump at release.

mykola-mokhnach and others added 3 commits October 5, 2026 10:50
Converts the build to a multi-project one. The core stays at the root, and
the new selenium-bridge subproject publishes io.appium:java-client-selenium-bridge
with the version of the core. It depends on the core and on
selenium-remote-driver within the same range as selenium-api, so both resolve
to one Selenium version.

The repositories and the common POM data are shared by both projects, and
JReleaser stages the artifacts of both. CI needs no change, because the
e2eAndroidTest and e2eIosTest tasks exist in both projects and run together.
The publication was verified with a JReleaser dry run: one Maven Central
bundle that contains both modules.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
AppiumDriver is not a RemoteWebDriver anymore and does not implement HasBiDi.
SeleniumBridge.asRemoteWebDriver adapts it for the Selenium code that needs
them, for example the Augmenter and the BiDi modules. The result is a
RemoteWebDriver attached to the session of the Appium driver. Its commands go
through the command executor of the Appium driver, so both drivers share the
session and the HTTP client, and quit() quits the session.

BiDi is created on the first use from the webSocketUrl capability, with the
timeouts, proxy, credentials and SSL context of the AppiumClientConfig. The
overload with an HttpClient.Factory customizes the WebSocket client.
maybeGetBiDi() is overridden without @OverRide, because Selenium's modules still
call it and Selenium is going to delete it. getHandle() is the real override.

AppiumCommandExecutor#getClientConfig is added to the core for this.

Tests run against a fake Appium HTTP server and a fake BiDi WebSocket server.
The BiDi e2e tests that were removed from the core are restored here.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Adds the Selenium interoperability page and links it from the README and the
v10 to v11 migration guide.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The bridge overrode maybeGetBiDi(), which Selenium deprecated for removal,
and attached to the session through protected RemoteWebDriver hooks. Build
BridgedRemoteWebDriver with the public RemoteWebDriver constructor instead.
The command executor answers the new session command with the existing
Appium session and capabilities, so Selenium takes the session over and
creates the BiDi connection itself from the webSocketUrl capability.

No deprecated Selenium API is overridden or called anymore (maybeGetBiDi,
Response#setStatus, getBiDi in the tests), so no warning needs suppressing,
and the module compiles with -Xlint:deprecation,removal. The e2e BiDi tests
use the LogInspector module for the same reason. BiDi now connects when the
bridge is created.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
mykola-mokhnach and others added 3 commits October 5, 2026 11:48
A new script resolves the versions to install from the npm dist-tags. If the
server and the platform driver both have a beta release that is newer than
their latest stable one, both are installed in the beta version, so
incompatibilities with the upcoming releases show up early. Otherwise the
stable versions are used. The Flutter tests always use the stable versions.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
simulator-action v6 can wait until the background CPU usage of the simulator
subsides. Waiting for up to 180 seconds avoids starting the tests while the
simulator is still busy. If it does not settle in time, the action only warns.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Comment thread docs/selenium-bridge.md Outdated
Comment thread src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java Outdated
Comment thread .github/workflows/ci.yml
harsha509 and others added 2 commits October 5, 2026 21:37
- Add a no-arg constructor to BridgedRemoteWebDriver, which the Selenium
  Augmenter calls on its generated subclass. Without it, any matching
  augmentation (for example the se:cdp capability) failed.
- Ignore the quit command until the bridge is created. Selenium quits the
  driver if its constructor fails, which ended the session of the Appium
  driver.
- Return the same bridge for the same Appium driver, so repeated calls do not
  open more BiDi connections, and add closeBiDi() to release the connection
  without quitting the session. quit() also releases the HTTP clients of the
  connection. The e2e tests and the docs close the connection now.
- Document that elements are not interchangeable between the two drivers.
- Drop AppiumCommandExecutor#getClientConfig in favor of the existing Lombok
  getter getAppiumClientConfig.

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

@harsha509 harsha509 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@mykola-mokhnach
mykola-mokhnach merged commit 4915d50 into master Oct 5, 2026
12 checks passed
@mykola-mokhnach
mykola-mokhnach deleted the stage6 branch October 5, 2026 17:48
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.

3 participants