Skip to content

[grid] honor client-advertised se:remoteUrl for reachable BiDi/CDP/VNC URLs#17790

Open
titusfortner wants to merge 2 commits into
SeleniumHQ:trunkfrom
titusfortner:grid-se-remote-url
Open

[grid] honor client-advertised se:remoteUrl for reachable BiDi/CDP/VNC URLs#17790
titusfortner wants to merge 2 commits into
SeleniumHQ:trunkfrom
titusfortner:grid-se-remote-url

Conversation

@titusfortner

Copy link
Copy Markdown
Member

🔗 Related Issues

Fixes #17782
Fixes #17788

💥 What does this PR do?

When a Grid is reached through Docker port-mapping or a reverse proxy, the BiDi/CDP/VNC URLs it hands back (webSocketUrl, se:cdp, se:vnc) point at the Grid's own view of its address — e.g. a container-internal ws://172.20.0.2:4444/... — which the client cannot reach, and causes attempted connections to error.

The Grid currently addresses this with a grid-url configuration (--grid-url / SE_NODE_GRID_URL): when set to the externally-reachable address the Node uses it for the proxied URLs. Except:

  • Users have to know this configuration exists as we've seen. People appear to be editing the capabilities themselves.
  • this requires knowing the full URL, including port when the grid is started, which means it won't work for containers with dynamically mapped ports as @AB-xdev reported in [🐛 Bug]: Do not establish BiDiConnection on startup #17782

This PR has each bindiung advertise the url they actually reached the Grid on via a new se:remoteUrl capability, which the Node uses to build the proxied URLs. Additional connections to Docker then work with no extra configuration. Note that an explicitly configured grid-url setting still takes precedence.

🔧 Implementation Notes

  • The fix lives in the Node (LocalNode.resolvePublicGridUri), so one server-side change makes the returned URLs reachable for every client; each binding only adds the small producer that sends se:remoteUrl.
  • Precedence is decided in resolvePublicGridUri: a configured public Grid URL (grid-url / hub) is used as-is; otherwise se:remoteUrl; otherwise the Node's auto-detected address (previous behavior). So the capability only fills the gap grid-url leaves and never overrides operator intent.
  • se:remoteUrl is not sent for local driver services (only for remote sessions), and when it is absent the behavior is unchanged for older clients.
  • Chose a client-advertised address over server-side Host-header inference: the client is authoritative about its own reachable address, it is robust behind proxies, and it avoids changes to Grid routing.

💡 Additional Considerations

  • Credentials embedded in the connection URL (https://user:key@host) are preserved in the resolved URL, identical to how an operator-configured grid-url with credentials already behaves. This keeps BiDi/CDP auth working against a basic-auth-protected Grid, where the returned websocket URL must carry the credentials. Under HTTPS these are protected in transit; the only residual is at-rest logging, consistent with the Grid already logging full capabilities today. Reducing that would be a separate, Grid-wide log-redaction change rather than mangling the URL the auth path depends on.
  • Most service providers now recommend supplying credentials via capabilities and using ClientConfig to set local authentication and proxy behavior, in which case the connection URL carries no credentials and se:remoteUrl carries none.

🤖 AI assistance

  • No substantial AI assistance used
  • AI assisted (complete below)
    • Tool(s): Claude Code
    • What was generated: implementation and tests across all bindings
    • I reviewed all AI output and can explain the change

🔄 Types of changes

  • Bug fix (backwards compatible)

@selenium-ci selenium-ci added B-grid Everything grid and server related C-py Python Bindings C-rb Ruby Bindings C-dotnet .NET Bindings C-java Java Bindings C-nodejs JavaScript Bindings labels Jul 16, 2026
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Grid: use client-advertised se:remoteUrl for reachable BiDi/CDP/VNC endpoints

🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Advertise the client-reachable Grid URL via a new se:remoteUrl session capability.
• Prefer grid-url, else se:remoteUrl, else auto-detected address when rewriting proxied
 endpoints.
• Add cross-binding and server-side tests covering precedence and credential preservation.
Diagram

graph TD
  A["Client bindings"] --> B["NEW_SESSION caps include se:remoteUrl"] --> C["Grid Node LocalNode"] --> D{"grid-url configured?"}
  D -->|"Yes"| E["Use grid-url"] --> I["Rewrite se:cdp/webSocketUrl/se:vnc"] --> J["Session response endpoints"]
  D -->|"No"| F{"se:remoteUrl valid?"}
  F -->|"Yes"| G["Use se:remoteUrl"] --> I
  F -->|"No"| H["Use auto gridUri"] --> I
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Infer public base URL from Host/X-Forwarded-* headers
  • ➕ No client-side changes required
  • ➕ Keeps capability payload unchanged
  • ➖ Proxy/header setups vary; easy to misconfigure and hard to validate
  • ➖ Requires Grid routing/proxy awareness and consistent forwarded-header handling across deployments
  • ➖ Less authoritative than the client’s actual reachable URL
2. Extend `grid-url` to support dynamic port mapping (templating/runtime discovery)
  • ➕ Keeps operator control centralized
  • ➕ Avoids passing connection URL in capabilities
  • ➖ Still requires users to discover/configure it correctly
  • ➖ Runtime discovery across Docker/proxy topologies is environment-specific and brittle
  • ➖ Doesn’t help when multiple public entrypoints exist
3. Always trust `se:remoteUrl` over `grid-url`
  • ➕ Maximizes client control; simplest rule
  • ➖ Violates operator intent and makes behavior harder to reason about in managed Grids
  • ➖ Can introduce security/observability concerns if clients spoof URLs

Recommendation: The chosen approach (client-advertised se:remoteUrl with strict precedence: configured grid-url first, then se:remoteUrl, then fallback) is the best trade-off. It fixes Docker/reverse-proxy reachability without new server-side proxy inference and without overriding explicit operator configuration; tests also lock in precedence and credential-preserving behavior.

Files changed (13) +245 / -12

Enhancement (3) +23 / -1
HttpCommandExecutor.csExpose remote server URI to callers +5/-0

Expose remote server URI to callers

• Adds an internal 'RemoteServerUri' accessor so higher-level session creation can reliably retrieve the exact address used for remote connections.

dotnet/src/webdriver/Remote/HttpCommandExecutor.cs

RemoteWebDriver.javaSend se:remoteUrl from ClientConfig baseUri on session start +11/-0

Send se:remoteUrl from ClientConfig baseUri on session start

• Adds 'se:remoteUrl' to requested capabilities when 'ClientConfig.baseUri()' is set, ensuring the server can return client-reachable proxied endpoints.

java/src/org/openqa/selenium/remote/RemoteWebDriver.java

RemoteWebDriverBuilder.javaInclude se:remoteUrl in alwaysMatch payload +7/-1

Include se:remoteUrl in alwaysMatch payload

• Copies 'additionalCapabilities' into a new alwaysMatch map and appends 'se:remoteUrl' using the builder base URI before building the W3C new-session payload.

java/src/org/openqa/selenium/remote/RemoteWebDriverBuilder.java

Bug fix (5) +50 / -7
WebDriver.csAdvertise se:remoteUrl when starting remote sessions +5/-0

Advertise se:remoteUrl when starting remote sessions

• When using 'Remote.HttpCommandExecutor', injects 'se:remoteUrl' into the W3C capabilities payload based on the executor’s remote server address.

dotnet/src/webdriver/WebDriver.cs

LocalNode.javaRewrite BiDi/CDP/VNC URLs using resolved public Grid URI +29/-7

Rewrite BiDi/CDP/VNC URLs using resolved public Grid URI

• Refactors URL rewriting to accept a chosen base URI and introduces 'resolvePublicGridUri' to apply precedence: configured 'grid-url' > client 'se:remoteUrl' > prior auto-detected address. Preserves scheme (ws/wss), path prefix, port, and userinfo when constructing proxied endpoints.

java/src/org/openqa/selenium/grid/node/local/LocalNode.java

index.jsAdd se:remoteUrl to capabilities for remote Builder URL strings +5/-0

Add se:remoteUrl to capabilities for remote Builder URL strings

• When the remote server URL is a string, sets 'se:remoteUrl' on the outgoing capabilities before creating the HTTP client/executor.

javascript/selenium-webdriver/index.js

webdriver.pyAdvertise se:remoteUrl for remote (non-service) sessions +10/-0

Advertise se:remoteUrl for remote (non-service) sessions

• Injects 'se:remoteUrl' into capabilities when the driver is connecting to a remote server (and not using a local 'service'). Adds a helper to retrieve the remote address from the command executor client config.

py/selenium/webdriver/remote/webdriver.py

driver.rbAdvertise se:remoteUrl from resolved server_url +1/-0

Advertise se:remoteUrl from resolved server_url

• Sets 'caps['se:remoteUrl']' to the HTTP client’s resolved 'server_url', ensuring remote sessions carry the client-reachable Grid URL.

rb/lib/selenium/webdriver/remote/driver.rb

Tests (5) +172 / -4
NodeTest.javaTest se:remoteUrl precedence and credential preservation +130/-0

Test se:remoteUrl precedence and credential preservation

• Adds coverage verifying 'se:remoteUrl' is used for proxied endpoints when provided, ignored when 'grid-url' is configured, and that userinfo (credentials) is preserved in rewritten URLs.

java/test/org/openqa/selenium/grid/node/NodeTest.java

RemoteWebDriverBuilderTest.javaVerify builder advertises se:remoteUrl +18/-0

Verify builder advertises se:remoteUrl

• Adds a unit test asserting the builder includes 'se:remoteUrl' in the capabilities sent to the server.

java/test/org/openqa/selenium/remote/RemoteWebDriverBuilderTest.java

RemoteWebDriverUnitTest.javaVerify RemoteWebDriver advertises se:remoteUrl +14/-0

Verify RemoteWebDriver advertises se:remoteUrl

• Adds a unit test ensuring 'RemoteWebDriver' includes 'se:remoteUrl' derived from 'ClientConfig.baseUri()' during session creation.

java/test/org/openqa/selenium/remote/RemoteWebDriverUnitTest.java

new_session_tests.pyUpdate new-session test to tolerate se:remoteUrl injection +6/-2

Update new-session test to tolerate se:remoteUrl injection

• Adjusts the capability assertions to remove 'se:remoteUrl' before comparing, keeping existing expectations stable while allowing the new capability.

py/test/unit/selenium/webdriver/remote/new_session_tests.py

driver_spec.rbUpdate remote driver spec to expect se:remoteUrl +4/-2

Update remote driver spec to expect se:remoteUrl

• Modifies request stubbing to automatically include 'se:remoteUrl' in the outgoing alwaysMatch payload, matching the new driver behavior.

rb/spec/unit/selenium/webdriver/remote/driver_spec.rb

@qodo-code-review

qodo-code-review Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (3) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 39 rules

Grey Divider


Action required

1. gridUrlSpecified() missing Javadoc 📘 Rule violation ✧ Quality ⭐ New
Description
The new public method LocalNode.Builder.gridUrlSpecified(boolean configured) lacks a Javadoc block
with @param/@return, violating the requirement for complete Javadoc on public API methods. This
reduces API clarity and makes the builder contract harder to maintain.
Code

java/src/org/openqa/selenium/grid/node/local/LocalNode.java[R1577-1580]

+    public Builder gridUrlSpecified(boolean configured) {
+      this.gridUrlSpecified = configured;
+      return this;
+    }
Evidence
PR Compliance ID 330201 requires complete Javadoc for changed/added public methods. The added public
builder method gridUrlSpecified(boolean configured) has no Javadoc directly above it.

Rule 330201: Require complete Javadoc on public API methods
java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1577-1580]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new public method was added without the required complete Javadoc (description + `@param` + `@return`).

## Issue Context
`LocalNode.Builder.gridUrlSpecified(boolean configured)` is public and should be documented per the project’s Javadoc requirements.

## Fix Focus Areas
- java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1577-1580]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. LocalNode constructor signature changed 📘 Rule violation ≡ Correctness ⭐ New
Description
The protected LocalNode(...) constructor now requires an additional boolean gridUrlSpecified
parameter, which is a potentially breaking ABI/API change for any external subclasses calling
super(...). Consider adding a backward-compatible overload delegating to the new constructor to
preserve compatibility.
Code

java/src/org/openqa/selenium/grid/node/local/LocalNode.java[R176-180]

      EventBus bus,
      URI uri,
      URI gridUri,
+      boolean gridUrlSpecified,
      @Nullable HealthCheck healthCheck,
Evidence
PR Compliance ID 389266 requires maintaining backward-compatible public API/ABI surfaces. The
constructor signature was modified by adding the gridUrlSpecified parameter, which changes the
callable signature for subclasses.

Rule 389266: Maintain backward-compatible public API and ABI
java/src/org/openqa/selenium/grid/node/local/LocalNode.java[174-205]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new required parameter was added to a `protected` constructor, which can break downstream subclasses compiled against the previous signature.

## Issue Context
Even though call sites in-repo were updated, `protected` constructors are part of the subclassing surface and can impact external users.

## Fix Focus Areas
- java/src/org/openqa/selenium/grid/node/local/LocalNode.java[174-205]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. RemoteUrl scheme not validated 🐞 Bug ≡ Correctness ⭐ New
Description
LocalNode.resolvePublicGridUri will accept any se:remoteUrl URI that merely has a non-null host,
even if it’s not an absolute http/https URL. This can produce incorrect ws/wss URL construction (and
can break path handling) when the accepted URI is later consumed by rewrite().
Code

java/src/org/openqa/selenium/grid/node/local/LocalNode.java[R1303-1321]

+  // A configured grid-url always wins; only when the node falls back to its auto-detected address
+  // (which may be unreachable behind Docker/proxy) do we use the client-advertised se:remoteUrl.
+  private URI resolvePublicGridUri(Capabilities caps) {
+    if (gridUrlSpecified) {
+      return gridUri;
+    }
+    Object raw = caps.getCapability("se:remoteUrl");
+    if (raw instanceof String && !((String) raw).isEmpty()) {
+      try {
+        URI uri = new URI((String) raw);
+        if (uri.getHost() != null) {
+          return uri;
+        }
+      } catch (URISyntaxException e) {
+        // Fall back to gridUri.
+      }
+    }
+    return gridUri;
+  }
Evidence
The new resolver returns any URI with a host, but rewrite() assumes an http/https-like base and
uses its scheme/path to generate websocket URLs. Separately, NodeOptions.normalizeSubPath calls
trim() on its input, which will throw if a null path ever reaches it.

java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1292-1321]
java/src/org/openqa/selenium/grid/node/config/NodeOptions.java[171-184]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`resolvePublicGridUri` currently accepts any `se:remoteUrl` string that parses as a `URI` with a non-null host. Because this URI is later used as the base for websocket URL generation, accepting scheme-less or non-http(s) URIs can lead to incorrect ws/wss selection and/or invalid path normalization.

### Issue Context
Downstream, `rewrite()` derives websocket scheme from `baseUri.getScheme()` and normalizes `baseUri.getPath()`.

### Fix Focus Areas
- java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1303-1321]

### Suggested fix
- In `resolvePublicGridUri`, require an absolute URI with an explicit scheme of `http` or `https` (and a non-null host) before returning it.
- Optionally harden `rewrite()` to treat a null path as empty (defensive coding), but the key is to avoid returning invalid bases from `resolvePublicGridUri`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. gridUrlSpecified footgun 🐞 Bug ⚙ Maintainability ⭐ New
Description
LocalNode.Builder defaults gridUrlSpecified to false and requires callers to set it separately
from the gridUri they pass. Any caller that supplies a configured/public gridUri but forgets to
set gridUrlSpecified(true) will silently allow client se:remoteUrl to override what the caller
intended to be authoritative.
Code

java/src/org/openqa/selenium/grid/node/local/LocalNode.java[R1577-1581]

+    public Builder gridUrlSpecified(boolean configured) {
+      this.gridUrlSpecified = configured;
+      return this;
+    }
+
Evidence
The builder stores gridUri and gridUrlSpecified independently, with the flag defaulting to
false, so callers can accidentally create contradictory state. There are existing call sites that
pass a publicUri distinct from the node URI without setting the new flag, demonstrating the ease
of omission.

java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1476-1505]
java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1577-1603]
java/test/org/openqa/selenium/grid/graphql/GraphqlHandlerTest.java[255-305]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The precedence decision in `resolvePublicGridUri` depends on `gridUrlSpecified`, but the builder API lets callers pass a `gridUri` while leaving `gridUrlSpecified` at its default `false`. This creates an easy-to-miss misconfiguration where an explicitly supplied public grid URI can be overridden by client-provided `se:remoteUrl`.

### Issue Context
`LocalNodeFactory` sets the flag correctly, but other builder call sites (tests/extensions) can pass a non-default `gridUri` without remembering to also set the boolean.

### Fix Focus Areas
- java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1485-1505]
- java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1577-1588]

### Suggested fix
Implement one of:
1) Make `gridUrlSpecified` derive from constructor inputs by default (e.g., initialize it in `Builder` based on whether `gridUri` differs from `uri`), while still allowing explicit override for the "equal URIs but configured" edge case.
2) Change the builder API to make the configured-vs-auto-detected nature explicit (e.g., introduce a dedicated `publicGridUri(URI configuredUri)` setter that both stores the URI and marks it configured, instead of a separate boolean).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
5. RemoteUrl added for local 🐞 Bug ☼ Reliability
Description
Java RemoteWebDriver injects se:remoteUrl into every new-session request when
clientConfig.baseUri() is non-null, and DriverCommandExecutor sets baseUri to the local
DriverService URL. As a result, local drivers like ChromeDriver will send se:remoteUrl
pointing at the local driver service, and any user-provided se:remoteUrl capability is
overwritten.
Code

java/src/org/openqa/selenium/remote/RemoteWebDriver.java[R263-277]

+  private Capabilities addRemoteUrl(Capabilities capabilities) {
+    URI baseUri = clientConfig.baseUri();
+    if (baseUri == null) {
+      return capabilities;
+    }
+    MutableCapabilities withRemoteUrl = new MutableCapabilities(capabilities);
+    withRemoteUrl.setCapability("se:remoteUrl", baseUri.toString());
+    return withRemoteUrl;
+  }
+
  protected void startSession(Capabilities capabilities) {
    checkNonW3CCapabilities(capabilities);
    checkChromeW3CFalse(capabilities);
+    capabilities = addRemoteUrl(capabilities);
Evidence
RemoteWebDriver adds se:remoteUrl whenever clientConfig.baseUri() is set.
DriverCommandExecutor explicitly sets clientConfig.baseUri(service.getUrl().toURI()), and local
drivers (e.g., Chrome) use DriverCommandExecutor subclasses and inherit
RemoteWebDriver.startSession, so local driver sessions will carry this capability.

java/src/org/openqa/selenium/remote/RemoteWebDriver.java[263-277]
java/src/org/openqa/selenium/remote/service/DriverCommandExecutor.java[84-102]
java/src/org/openqa/selenium/chromium/ChromiumDriverCommandExecutor.java[35-50]
java/src/org/openqa/selenium/chrome/ChromeDriver.java[90-97]
java/src/org/openqa/selenium/chromium/ChromiumDriver.java[68-109]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`RemoteWebDriver.startSession` now unconditionally calls `addRemoteUrl`, which injects `se:remoteUrl` whenever `clientConfig.baseUri()` is present. Local driver implementations use `DriverCommandExecutor`, which populates `clientConfig.baseUri()` with the local `DriverService` URL, so `se:remoteUrl` is sent to local driver services too.

### Issue Context
`se:remoteUrl` is intended to describe how the *client reached the Grid*, not how Selenium reached a local `DriverService`. Injecting it for local-driver sessions provides a misleading value and overwrites any explicit user-supplied `se:remoteUrl`.

### Fix Focus Areas
- java/src/org/openqa/selenium/remote/RemoteWebDriver.java[263-277]

### Implementation sketch
- In `addRemoteUrl(...)` (or before calling it), detect local driver-service sessions via `executor instanceof org.openqa.selenium.remote.service.DriverCommandExecutor` (and possibly known subclasses) and return capabilities unchanged.
- Keep current behavior for true remote sessions created with `HttpCommandExecutor`/`TracedCommandExecutor`.
- Consider adding a unit test that builds a `RemoteWebDriver` with a `DriverCommandExecutor` and asserts the outgoing NEW_SESSION payload does not contain `se:remoteUrl`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Grid-url precedence misdetected ✓ Resolved 🐞 Bug ≡ Correctness
Description
LocalNode.resolvePublicGridUri decides whether a public grid-url is configured by checking
!gridUri.equals(externalUri), so an explicitly configured grid-url that happens to equal
externalUri will be treated as “not configured” and can be overridden by client se:remoteUrl.
This violates the intended precedence and can yield incorrect proxied BiDi/CDP/VNC URLs even though
an operator configured grid-url.
Code

java/src/org/openqa/selenium/grid/node/local/LocalNode.java[R1300-1318]

+  // A configured grid-url always wins; only when the node falls back to its auto-detected address
+  // (which may be unreachable behind Docker/proxy) do we use the client-advertised se:remoteUrl.
+  private URI resolvePublicGridUri(Capabilities caps) {
+    if (!gridUri.equals(externalUri)) {
+      return gridUri;
+    }
+    Object raw = caps.getCapability("se:remoteUrl");
+    if (raw instanceof String && !((String) raw).isEmpty()) {
+      try {
+        URI uri = new URI((String) raw);
+        if (uri.getHost() != null) {
+          return uri;
+        }
+      } catch (URISyntaxException e) {
+        // Fall back to gridUri.
+      }
+    }
+    return gridUri;
+  }
Evidence
The Node currently has enough information at construction time to know whether gridUri was
explicitly configured (node.grid-url/node.hub) or just defaulted, but resolvePublicGridUri
instead uses gridUri.equals(externalUri) as a proxy for “configured”. If the configured value
equals externalUri, the method will incorrectly fall through to client se:remoteUrl.

java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1289-1318]
java/src/org/openqa/selenium/grid/node/local/LocalNodeFactory.java[65-72]
java/src/org/openqa/selenium/grid/node/config/NodeOptions.java[108-123]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`LocalNode.resolvePublicGridUri` infers whether `gridUri` came from an explicit operator configuration by comparing `gridUri` to `externalUri`. This loses provenance information when the configured `grid-url` equals `externalUri`.

### Issue Context
`LocalNodeFactory` already knows whether `gridUri` came from `nodeOptions.getPublicGridUri()` (configured) or from the fallback `serverOptions.getExternalUri()`. That boolean should be passed into `LocalNode` instead of reconstructing intent from URI equality.

### Fix Focus Areas
- java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1300-1318]
- java/src/org/openqa/selenium/grid/node/local/LocalNodeFactory.java[65-72]

### Implementation sketch
- Change `LocalNode.builder(...)` / `LocalNode` ctor to accept a boolean like `isPublicGridUriConfigured` (or accept `Optional<URI> configuredGridUri`).
- In `resolvePublicGridUri`, use that flag/optional to decide whether to always return the configured value.
- Add a regression test where configured `gridUri` equals `externalUri` and assert client `se:remoteUrl` does **not** override it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. new_session_tests patches WebDriver.execute 📘 Rule violation ▣ Testability
Description
The updated tests mock remote WebDriver session/HTTP interactions using pytest-mock (patching
WebDriver.execute) and Ruby WebMock (stub_request) rather than exercising a real integration or
a contract-driven stub. This reduces test fidelity and violates the requirement to avoid mocks for
external/service integrations unless they are backed by a machine-checked contract.
Code

py/test/unit/selenium/webdriver/remote/new_session_tests.py[R37-42]

+    command, params = mock.call_args[0]
+    assert command == Command.NEW_SESSION
+    always_match = params["capabilities"]["alwaysMatch"]
+    always_match.pop("se:remoteUrl", None)
+    assert params["capabilities"]["firstMatch"] == [{}]
+    assert always_match == w3c_caps
Evidence
PR Compliance ID 389270 disallows using mocking frameworks for external/service interactions unless
the stub is contract-driven. The Python unit test patches
selenium.webdriver.remote.webdriver.WebDriver.execute and inspects mock.call_args, demonstrating
the remote interaction is simulated rather than validated against a real or contract-backed
endpoint, and the Ruby helper similarly configures stub_request(:post, endpoint), which is an
HTTP-level mock of the remote session creation endpoint rather than a real/contract-verified
integration.

Rule 389270: Avoid mocks in tests; use real or contract-driven integrations
py/test/unit/selenium/webdriver/remote/new_session_tests.py[30-42]
rb/spec/unit/selenium/webdriver/remote/driver_spec.rb[32-37]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The tests currently rely on mocking frameworks (`pytest-mock` patching `selenium.webdriver.remote.webdriver.WebDriver.execute` and Ruby WebMock `stub_request`) to simulate remote WebDriver session/HTTP interactions. Update these tests to avoid hand-written mocks for external/service integrations unless the stubs are backed by a machine-checked contract, and instead use a real integration approach or a contract-driven stub.

## Issue Context
Compliance (PR Compliance ID 389270) requires avoiding mocks for external API/service interactions unless they are backed by a contract that ensures the stub matches the real API. The current changes simulate the remote new-session flow by patching `WebDriver.execute`/asserting on `mock.call_args` in Python and by stubbing HTTP POST requests via `stub_request` in Ruby, which reduces fidelity and is not contract-driven.

## Fix Focus Areas
- py/test/unit/selenium/webdriver/remote/new_session_tests.py[30-42]
- rb/spec/unit/selenium/webdriver/remote/driver_spec.rb[32-37]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Previous review results

Review updated until commit 9a20e5d

Results up to commit b3412c0


🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Remediation recommended
1. new_session_tests patches WebDriver.execute 📘 Rule violation ▣ Testability
Description
The updated tests mock remote WebDriver session/HTTP interactions using pytest-mock (patching
WebDriver.execute) and Ruby WebMock (stub_request) rather than exercising a real integration or
a contract-driven stub. This reduces test fidelity and violates the requirement to avoid mocks for
external/service integrations unless they are backed by a machine-checked contract.
Code

py/test/unit/selenium/webdriver/remote/new_session_tests.py[R37-42]

+    command, params = mock.call_args[0]
+    assert command == Command.NEW_SESSION
+    always_match = params["capabilities"]["alwaysMatch"]
+    always_match.pop("se:remoteUrl", None)
+    assert params["capabilities"]["firstMatch"] == [{}]
+    assert always_match == w3c_caps
Evidence
PR Compliance ID 389270 disallows using mocking frameworks for external/service interactions unless
the stub is contract-driven. The Python unit test patches
selenium.webdriver.remote.webdriver.WebDriver.execute and inspects mock.call_args, demonstrating
the remote interaction is simulated rather than validated against a real or contract-backed
endpoint, and the Ruby helper similarly configures stub_request(:post, endpoint), which is an
HTTP-level mock of the remote session creation endpoint rather than a real/contract-verified
integration.

Rule 389270: Avoid mocks in tests; use real or contract-driven integrations
py/test/unit/selenium/webdriver/remote/new_session_tests.py[30-42]
rb/spec/unit/selenium/webdriver/remote/driver_spec.rb[32-37]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The tests currently rely on mocking frameworks (`pytest-mock` patching `selenium.webdriver.remote.webdriver.WebDriver.execute` and Ruby WebMock `stub_request`) to simulate remote WebDriver session/HTTP interactions. Update these tests to avoid hand-written mocks for external/service integrations unless the stubs are backed by a machine-checked contract, and instead use a real integration approach or a contract-driven stub.

## Issue Context
Compliance (PR Compliance ID 389270) requires avoiding mocks for external API/service interactions unless they are backed by a contract that ensures the stub matches the real API. The current changes simulate the remote new-session flow by patching `WebDriver.execute`/asserting on `mock.call_args` in Python and by stubbing HTTP POST requests via `stub_request` in Ruby, which reduces fidelity and is not contract-driven.

## Fix Focus Areas
- py/test/unit/selenium/webdriver/remote/new_session_tests.py[30-42]
- rb/spec/unit/selenium/webdriver/remote/driver_spec.rb[32-37]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. RemoteUrl added for local 🐞 Bug ☼ Reliability
Description
Java RemoteWebDriver injects se:remoteUrl into every new-session request when
clientConfig.baseUri() is non-null, and DriverCommandExecutor sets baseUri to the local
DriverService URL. As a result, local drivers like ChromeDriver will send se:remoteUrl
pointing at the local driver service, and any user-provided se:remoteUrl capability is
overwritten.
Code

java/src/org/openqa/selenium/remote/RemoteWebDriver.java[R263-277]

+  private Capabilities addRemoteUrl(Capabilities capabilities) {
+    URI baseUri = clientConfig.baseUri();
+    if (baseUri == null) {
+      return capabilities;
+    }
+    MutableCapabilities withRemoteUrl = new MutableCapabilities(capabilities);
+    withRemoteUrl.setCapability("se:remoteUrl", baseUri.toString());
+    return withRemoteUrl;
+  }
+
  protected void startSession(Capabilities capabilities) {
    checkNonW3CCapabilities(capabilities);
    checkChromeW3CFalse(capabilities);
+    capabilities = addRemoteUrl(capabilities);
Evidence
RemoteWebDriver adds se:remoteUrl whenever clientConfig.baseUri() is set.
DriverCommandExecutor explicitly sets clientConfig.baseUri(service.getUrl().toURI()), and local
drivers (e.g., Chrome) use DriverCommandExecutor subclasses and inherit
RemoteWebDriver.startSession, so local driver sessions will carry this capability.

java/src/org/openqa/selenium/remote/RemoteWebDriver.java[263-277]
java/src/org/openqa/selenium/remote/service/DriverCommandExecutor.java[84-102]
java/src/org/openqa/selenium/chromium/ChromiumDriverCommandExecutor.java[35-50]
java/src/org/openqa/selenium/chrome/ChromeDriver.java[90-97]
java/src/org/openqa/selenium/chromium/ChromiumDriver.java[68-109]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`RemoteWebDriver.startSession` now unconditionally calls `addRemoteUrl`, which injects `se:remoteUrl` whenever `clientConfig.baseUri()` is present. Local driver implementations use `DriverCommandExecutor`, which populates `clientConfig.baseUri()` with the local `DriverService` URL, so `se:remoteUrl` is sent to local driver services too.

### Issue Context
`se:remoteUrl` is intended to describe how the *client reached the Grid*, not how Selenium reached a local `DriverService`. Injecting it for local-driver sessions provides a misleading value and overwrites any explicit user-supplied `se:remoteUrl`.

### Fix Focus Areas
- java/src/org/openqa/selenium/remote/RemoteWebDriver.java[263-277]

### Implementation sketch
- In `addRemoteUrl(...)` (or before calling it), detect local driver-service sessions via `executor instanceof org.openqa.selenium.remote.service.DriverCommandExecutor` (and possibly known subclasses) and return capabilities unchanged.
- Keep current behavior for true remote sessions created with `HttpCommandExecutor`/`TracedCommandExecutor`.
- Consider adding a unit test that builds a `RemoteWebDriver` with a `DriverCommandExecutor` and asserts the outgoing NEW_SESSION payload does not contain `se:remoteUrl`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Grid-url precedence misdetected ✓ Resolved 🐞 Bug ≡ Correctness
Description
LocalNode.resolvePublicGridUri decides whether a public grid-url is configured by checking
!gridUri.equals(externalUri), so an explicitly configured grid-url that happens to equal
externalUri will be treated as “not configured” and can be overridden by client se:remoteUrl.
This violates the intended precedence and can yield incorrect proxied BiDi/CDP/VNC URLs even though
an operator configured grid-url.
Code

java/src/org/openqa/selenium/grid/node/local/LocalNode.java[R1300-1318]

+  // A configured grid-url always wins; only when the node falls back to its auto-detected address
+  // (which may be unreachable behind Docker/proxy) do we use the client-advertised se:remoteUrl.
+  private URI resolvePublicGridUri(Capabilities caps) {
+    if (!gridUri.equals(externalUri)) {
+      return gridUri;
+    }
+    Object raw = caps.getCapability("se:remoteUrl");
+    if (raw instanceof String && !((String) raw).isEmpty()) {
+      try {
+        URI uri = new URI((String) raw);
+        if (uri.getHost() != null) {
+          return uri;
+        }
+      } catch (URISyntaxException e) {
+        // Fall back to gridUri.
+      }
+    }
+    return gridUri;
+  }
Evidence
The Node currently has enough information at construction time to know whether gridUri was
explicitly configured (node.grid-url/node.hub) or just defaulted, but resolvePublicGridUri
instead uses gridUri.equals(externalUri) as a proxy for “configured”. If the configured value
equals externalUri, the method will incorrectly fall through to client se:remoteUrl.

java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1289-1318]
java/src/org/openqa/selenium/grid/node/local/LocalNodeFactory.java[65-72]
java/src/org/openqa/selenium/grid/node/config/NodeOptions.java[108-123]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`LocalNode.resolvePublicGridUri` infers whether `gridUri` came from an explicit operator configuration by comparing `gridUri` to `externalUri`. This loses provenance information when the configured `grid-url` equals `externalUri`.

### Issue Context
`LocalNodeFactory` already knows whether `gridUri` came from `nodeOptions.getPublicGridUri()` (configured) or from the fallback `serverOptions.getExternalUri()`. That boolean should be passed into `LocalNode` instead of reconstructing intent from URI equality.

### Fix Focus Areas
- java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1300-1318]
- java/src/org/openqa/selenium/grid/node/local/LocalNodeFactory.java[65-72]

### Implementation sketch
- Change `LocalNode.builder(...)` / `LocalNode` ctor to accept a boolean like `isPublicGridUriConfigured` (or accept `Optional<URI> configuredGridUri`).
- In `resolvePublicGridUri`, use that flag/optional to decide whether to always return the configured value.
- Add a regression test where configured `gridUri` equals `externalUri` and assert client `se:remoteUrl` does **not** override it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Qodo Logo

Comment thread py/test/unit/selenium/webdriver/remote/new_session_tests.py
Comment thread java/src/org/openqa/selenium/grid/node/local/LocalNode.java
Comment thread java/src/org/openqa/selenium/remote/RemoteWebDriver.java
Comment on lines +1577 to +1580
public Builder gridUrlSpecified(boolean configured) {
this.gridUrlSpecified = configured;
return this;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. gridurlspecified() missing javadoc 📘 Rule violation ✧ Quality

The new public method LocalNode.Builder.gridUrlSpecified(boolean configured) lacks a Javadoc block
with @param/@return, violating the requirement for complete Javadoc on public API methods. This
reduces API clarity and makes the builder contract harder to maintain.
Agent Prompt
## Issue description
A new public method was added without the required complete Javadoc (description + `@param` + `@return`).

## Issue Context
`LocalNode.Builder.gridUrlSpecified(boolean configured)` is public and should be documented per the project’s Javadoc requirements.

## Fix Focus Areas
- java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1577-1580]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 176 to 180
EventBus bus,
URI uri,
URI gridUri,
boolean gridUrlSpecified,
@Nullable HealthCheck healthCheck,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remediation recommended

2. localnode constructor signature changed 📘 Rule violation ≡ Correctness

The protected LocalNode(...) constructor now requires an additional boolean gridUrlSpecified
parameter, which is a potentially breaking ABI/API change for any external subclasses calling
super(...). Consider adding a backward-compatible overload delegating to the new constructor to
preserve compatibility.
Agent Prompt
## Issue description
A new required parameter was added to a `protected` constructor, which can break downstream subclasses compiled against the previous signature.

## Issue Context
Even though call sites in-repo were updated, `protected` constructors are part of the subclassing surface and can impact external users.

## Fix Focus Areas
- java/src/org/openqa/selenium/grid/node/local/LocalNode.java[174-205]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +1303 to +1321
// A configured grid-url always wins; only when the node falls back to its auto-detected address
// (which may be unreachable behind Docker/proxy) do we use the client-advertised se:remoteUrl.
private URI resolvePublicGridUri(Capabilities caps) {
if (gridUrlSpecified) {
return gridUri;
}
Object raw = caps.getCapability("se:remoteUrl");
if (raw instanceof String && !((String) raw).isEmpty()) {
try {
URI uri = new URI((String) raw);
if (uri.getHost() != null) {
return uri;
}
} catch (URISyntaxException e) {
// Fall back to gridUri.
}
}
return gridUri;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remediation recommended

3. Remoteurl scheme not validated 🐞 Bug ≡ Correctness

LocalNode.resolvePublicGridUri will accept any se:remoteUrl URI that merely has a non-null host,
even if it’s not an absolute http/https URL. This can produce incorrect ws/wss URL construction (and
can break path handling) when the accepted URI is later consumed by rewrite().
Agent Prompt
### Issue description
`resolvePublicGridUri` currently accepts any `se:remoteUrl` string that parses as a `URI` with a non-null host. Because this URI is later used as the base for websocket URL generation, accepting scheme-less or non-http(s) URIs can lead to incorrect ws/wss selection and/or invalid path normalization.

### Issue Context
Downstream, `rewrite()` derives websocket scheme from `baseUri.getScheme()` and normalizes `baseUri.getPath()`.

### Fix Focus Areas
- java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1303-1321]

### Suggested fix
- In `resolvePublicGridUri`, require an absolute URI with an explicit scheme of `http` or `https` (and a non-null host) before returning it.
- Optionally harden `rewrite()` to treat a null path as empty (defensive coding), but the key is to avoid returning invalid bases from `resolvePublicGridUri`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +1577 to +1581
public Builder gridUrlSpecified(boolean configured) {
this.gridUrlSpecified = configured;
return this;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remediation recommended

4. Gridurlspecified footgun 🐞 Bug ⚙ Maintainability

LocalNode.Builder defaults gridUrlSpecified to false and requires callers to set it separately
from the gridUri they pass. Any caller that supplies a configured/public gridUri but forgets to
set gridUrlSpecified(true) will silently allow client se:remoteUrl to override what the caller
intended to be authoritative.
Agent Prompt
### Issue description
The precedence decision in `resolvePublicGridUri` depends on `gridUrlSpecified`, but the builder API lets callers pass a `gridUri` while leaving `gridUrlSpecified` at its default `false`. This creates an easy-to-miss misconfiguration where an explicitly supplied public grid URI can be overridden by client-provided `se:remoteUrl`.

### Issue Context
`LocalNodeFactory` sets the flag correctly, but other builder call sites (tests/extensions) can pass a non-default `gridUri` without remembering to also set the boolean.

### Fix Focus Areas
- java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1485-1505]
- java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1577-1588]

### Suggested fix
Implement one of:
1) Make `gridUrlSpecified` derive from constructor inputs by default (e.g., initialize it in `Builder` based on whether `gridUri` differs from `uri`), while still allowing explicit override for the "equal URIs but configured" edge case.
2) Change the builder API to make the configured-vs-auto-detected nature explicit (e.g., introduce a dedicated `publicGridUri(URI configuredUri)` setter that both stores the URI and marks it configured, instead of a separate boolean).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 9a20e5d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

B-grid Everything grid and server related C-dotnet .NET Bindings C-java Java Bindings C-nodejs JavaScript Bindings C-py Python Bindings C-rb Ruby Bindings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[🐛 Bug]: Do not establish BiDiConnection on startup [🐛 Bug]: BiDi URL is not translated

2 participants