Skip to content

[dotnet] [bidi] Hide url required for websocket only#17208

Draft
nvborisenko wants to merge 3 commits intoSeleniumHQ:trunkfrom
nvborisenko:bidi-url
Draft

[dotnet] [bidi] Hide url required for websocket only#17208
nvborisenko wants to merge 3 commits intoSeleniumHQ:trunkfrom
nvborisenko:bidi-url

Conversation

@nvborisenko
Copy link
Member

This pull request refactors the BiDi connection setup in the .NET WebDriver codebase to improve clarity and enforce explicit transport configuration. The changes remove the implicit passing of the WebSocket URL, requiring users to specify the URL via the UseWebSocket method on BiDiOptionsBuilder. This makes the transport configuration more robust and less error-prone.

🔄 Types of changes

  • Cleanup (formatting, renaming)

Copilot AI review requested due to automatic review settings March 11, 2026 16:06
@selenium-ci selenium-ci added the C-dotnet .NET Bindings label Mar 11, 2026
@qodo-code-review
Copy link
Contributor

Review Summary by Qodo

Refactor BiDi connection to require explicit WebSocket URL configuration

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Refactors BiDi connection setup to require explicit URL configuration
• Removes implicit WebSocket URL passing from ConnectAsync method
• Makes UseWebSocket method accept URL parameter directly
• Enforces transport configuration through builder pattern

Grey Divider

File Changes

1. dotnet/src/webdriver/BiDi/BiDi.cs ✨ Enhancement +5/-3

Refactor ConnectAsync to require configuration action

• Changed ConnectAsync signature to require configure action parameter instead of optional url
 parameter
• Removed url parameter from method signature, making transport configuration mandatory
• Updated transport factory invocation to remove Uri argument
• Added null check for configure parameter

dotnet/src/webdriver/BiDi/BiDi.cs


2. dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs ✨ Enhancement +9/-10

Make WebSocket URL explicit in UseWebSocket method

• Changed TransportFactory from public property with default implementation to private field
• Modified UseWebSocket method to accept required url parameter
• Updated transport factory to store Uri internally and throw exception if not configured
• Updated XML documentation to reflect new URL parameter requirement

dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs


3. dotnet/src/webdriver/BiDi/WebDriver.Extensions.cs ✨ Enhancement +5/-1

Update extension method to use new URL configuration pattern

• Updated AsBiDiAsync to pass WebSocket URL through the configure action
• Modified to call UseWebSocket with the extracted webSocketUrl parameter
• Wrapped configuration logic to ensure URL is set before transport initialization

dotnet/src/webdriver/BiDi/WebDriver.Extensions.cs


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Mar 11, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. ConnectAsync signature changed 📘 Rule violation ✓ Correctness
Description
BiDi.ConnectAsync removed the url parameter and made configure non-optional, forcing consumer
code changes beyond a version bump. BiDiOptionsBuilder.UseWebSocket also now requires a url,
breaking existing callers and violating API/ABI compatibility expectations.
Code

dotnet/src/webdriver/BiDi/BiDi.cs[R56-63]

+    public static async Task<IBiDi> ConnectAsync(Action<BiDiOptionsBuilder> configure, CancellationToken cancellationToken = default)
    {
+        if (configure is null) throw new ArgumentNullException(nameof(configure));
+
        BiDiOptionsBuilder builder = new();
-        configure?.Invoke(builder);
+        configure(builder);

-        var transport = await builder.TransportFactory(new Uri(url), cancellationToken).ConfigureAwait(false);
+        var transport = await builder.TransportFactory(cancellationToken).ConfigureAwait(false);
Evidence
PR Compliance ID 1 requires public API/behavior to remain backward compatible for upgrades; the PR
changes public method signatures in BiDi and BiDiOptionsBuilder, requiring updates in downstream
code.

AGENTS.md
dotnet/src/webdriver/BiDi/BiDi.cs[56-63]
dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs[38-42]

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

## Issue description
Public API signatures were changed in a breaking way (`BiDi.ConnectAsync` and `BiDiOptionsBuilder.UseWebSocket`), requiring downstream user code changes.

## Issue Context
Compliance requires that users can upgrade by changing only the package version. If the new API is desired, keep it but add compatibility overloads that delegate to it.

## Fix Focus Areas
- dotnet/src/webdriver/BiDi/BiDi.cs[56-63]
- dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs[38-46]

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


2. Transport config overwritten 🐞 Bug ✓ Correctness
Description
WebDriverExtensions.AsBiDiAsync() always calls UseWebSocket(webSocketUrl) after running the
user-supplied configure callback, overwriting any transport configuration performed inside
configure. This makes caller transport/WebSocket configuration silently ineffective because the last
UseWebSocket call replaces the builder’s transport factory.
Code

dotnet/src/webdriver/BiDi/WebDriver.Extensions.cs[R37-41]

+        var bidi = await BiDi.ConnectAsync(options =>
+        {
+            configure?.Invoke(options);
+            options.UseWebSocket(webSocketUrl);
+        }, cancellationToken).ConfigureAwait(false);
Evidence
AsBiDiAsync executes the caller’s configure callback and then unconditionally sets the transport via
UseWebSocket(webSocketUrl). BiDiOptionsBuilder.UseWebSocket assigns the private _transportFactory
field, and TransportFactory returns that last-assigned value, so the unconditional second call wins
and discards any earlier transport configuration done inside configure.

dotnet/src/webdriver/BiDi/WebDriver.Extensions.cs[37-41]
dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs[38-42]
dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs[45-46]

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

### Issue description
`WebDriverExtensions.AsBiDiAsync()` currently runs the caller-provided `configure` callback and then unconditionally calls `options.UseWebSocket(webSocketUrl)`, which overwrites any transport configuration performed inside `configure`.

### Issue Context
`BiDiOptionsBuilder.UseWebSocket(...)` assigns `_transportFactory`, and only the last assignment is used. Therefore the unconditional `UseWebSocket(webSocketUrl)` call in `AsBiDiAsync` discards prior transport configuration.

### Fix Focus Areas
- dotnet/src/webdriver/BiDi/WebDriver.Extensions.cs[37-41]
- dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs[38-46]

### Suggested implementation direction
- Option A (non-breaking): after `configure?.Invoke(options)`, only call `UseWebSocket(webSocketUrl)` if transport was not configured.
 - This likely requires adding an `internal bool IsTransportConfigured =&gt; _transportFactory != null;` to `BiDiOptionsBuilder`.
- Option B: call `options.UseWebSocket(webSocketUrl)` first, then `configure?.Invoke(options)` to allow callers to override the transport (if they choose to).
- If you need to preserve the pre-refactor ergonomics (customizing `ClientWebSocketOptions` without needing to supply the URL), add a new overload of `AsBiDiAsync` that accepts `Action&lt;ClientWebSocketOptions&gt;?` and uses it in `UseWebSocket(webSocketUrl, configureWs)`.

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


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +56 to +63
public static async Task<IBiDi> ConnectAsync(Action<BiDiOptionsBuilder> configure, CancellationToken cancellationToken = default)
{
if (configure is null) throw new ArgumentNullException(nameof(configure));

BiDiOptionsBuilder builder = new();
configure?.Invoke(builder);
configure(builder);

var transport = await builder.TransportFactory(new Uri(url), cancellationToken).ConfigureAwait(false);
var transport = await builder.TransportFactory(cancellationToken).ConfigureAwait(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

1. connectasync signature changed 📘 Rule violation ✓ Correctness

BiDi.ConnectAsync removed the url parameter and made configure non-optional, forcing consumer
code changes beyond a version bump. BiDiOptionsBuilder.UseWebSocket also now requires a url,
breaking existing callers and violating API/ABI compatibility expectations.
Agent Prompt
## Issue description
Public API signatures were changed in a breaking way (`BiDi.ConnectAsync` and `BiDiOptionsBuilder.UseWebSocket`), requiring downstream user code changes.

## Issue Context
Compliance requires that users can upgrade by changing only the package version. If the new API is desired, keep it but add compatibility overloads that delegate to it.

## Fix Focus Areas
- dotnet/src/webdriver/BiDi/BiDi.cs[56-63]
- dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs[38-46]

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

Comment on lines +37 to +41
var bidi = await BiDi.ConnectAsync(options =>
{
configure?.Invoke(options);
options.UseWebSocket(webSocketUrl);
}, cancellationToken).ConfigureAwait(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

2. Transport config overwritten 🐞 Bug ✓ Correctness

WebDriverExtensions.AsBiDiAsync() always calls UseWebSocket(webSocketUrl) after running the
user-supplied configure callback, overwriting any transport configuration performed inside
configure. This makes caller transport/WebSocket configuration silently ineffective because the last
UseWebSocket call replaces the builder’s transport factory.
Agent Prompt
### Issue description
`WebDriverExtensions.AsBiDiAsync()` currently runs the caller-provided `configure` callback and then unconditionally calls `options.UseWebSocket(webSocketUrl)`, which overwrites any transport configuration performed inside `configure`.

### Issue Context
`BiDiOptionsBuilder.UseWebSocket(...)` assigns `_transportFactory`, and only the last assignment is used. Therefore the unconditional `UseWebSocket(webSocketUrl)` call in `AsBiDiAsync` discards prior transport configuration.

### Fix Focus Areas
- dotnet/src/webdriver/BiDi/WebDriver.Extensions.cs[37-41]
- dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs[38-46]

### Suggested implementation direction
- Option A (non-breaking): after `configure?.Invoke(options)`, only call `UseWebSocket(webSocketUrl)` if transport was not configured.
  - This likely requires adding an `internal bool IsTransportConfigured => _transportFactory != null;` to `BiDiOptionsBuilder`.
- Option B: call `options.UseWebSocket(webSocketUrl)` first, then `configure?.Invoke(options)` to allow callers to override the transport (if they choose to).
- If you need to preserve the pre-refactor ergonomics (customizing `ClientWebSocketOptions` without needing to supply the URL), add a new overload of `AsBiDiAsync` that accepts `Action<ClientWebSocketOptions>?` and uses it in `UseWebSocket(webSocketUrl, configureWs)`.

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

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the .NET BiDi connection setup to require explicit transport configuration via BiDiOptionsBuilder.UseWebSocket(url) rather than implicitly passing a WebSocket URL into BiDi.ConnectAsync.

Changes:

  • Updated WebDriverExtensions.AsBiDiAsync to configure BiDi connection by calling UseWebSocket(webSocketUrl) inside the options callback.
  • Refactored BiDiOptionsBuilder to store a configured transport factory and throw if transport is not configured.
  • Changed BiDi.ConnectAsync to require an options configuration callback and removed the url parameter overload.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
dotnet/src/webdriver/BiDi/WebDriver.Extensions.cs Switches BiDi connection creation to configure transport via builder callback.
dotnet/src/webdriver/BiDi/BiDiOptionsBuilder.cs Makes WebSocket URL explicit in UseWebSocket and enforces transport configuration.
dotnet/src/webdriver/BiDi/BiDi.cs Changes ConnectAsync signature to rely solely on builder configuration for transport.

Comment on lines +39 to +40
configure?.Invoke(options);
options.UseWebSocket(webSocketUrl);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

configure?.Invoke(options) runs before options.UseWebSocket(webSocketUrl), but UseWebSocket(...) overwrites the transport factory. If a caller uses configure to customize the WebSocket transport (headers/proxy/certs), their configuration will be discarded. Reorder/reshape this so caller configuration is preserved (or merge WebSocket option configuration rather than overwriting it).

Suggested change
configure?.Invoke(options);
options.UseWebSocket(webSocketUrl);
options.UseWebSocket(webSocketUrl);
configure?.Invoke(options);

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +40
var bidi = await BiDi.ConnectAsync(options =>
{
configure?.Invoke(options);
options.UseWebSocket(webSocketUrl);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

webSocketUrl is declared as string? and then captured into a lambda; nullable flow analysis generally won’t consider it non-null inside the delegate, which can produce nullable warnings (and potentially fail builds if warnings are treated as errors). Assign it to a non-null local (or use the null-forgiving operator after the null-check) before passing it into the lambda.

Suggested change
var bidi = await BiDi.ConnectAsync(options =>
{
configure?.Invoke(options);
options.UseWebSocket(webSocketUrl);
var nonNullWebSocketUrl = webSocketUrl;
var bidi = await BiDi.ConnectAsync(options =>
{
configure?.Invoke(options);
options.UseWebSocket(nonNullWebSocketUrl);

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +63
public static async Task<IBiDi> ConnectAsync(Action<BiDiOptionsBuilder> configure, CancellationToken cancellationToken = default)
{
if (configure is null) throw new ArgumentNullException(nameof(configure));

BiDiOptionsBuilder builder = new();
configure?.Invoke(builder);
configure(builder);

var transport = await builder.TransportFactory(new Uri(url), cancellationToken).ConfigureAwait(false);
var transport = await builder.TransportFactory(cancellationToken).ConfigureAwait(false);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

This change removes/changes the public BiDi.ConnectAsync(string url, ...) API, which violates the repo’s compatibility invariant (AGENTS.md:11-14) and deprecation guidance (AGENTS.md:65-67). To avoid breaking existing consumers, keep the existing overload (and behavior) and introduce the new overload alongside it, marking the old one [Obsolete] with a migration message instead of deleting/changing its signature.

Copilot uses AI. Check for mistakes.
Comment on lines 32 to 43
/// <summary>
/// Configures the BiDi connection to use a WebSocket transport.
/// Configures the BiDi connection to use a WebSocket transport with the specified URL.
/// </summary>
/// <remarks>
/// WebSocket is the default transport; calling this method is only necessary
/// when you need to customize the underlying <see cref="ClientWebSocketOptions"/>
/// (e.g., to set headers, proxy, or certificates).
/// </remarks>
/// <param name="url">The WebSocket URL to connect to.</param>
/// <param name="configure">An optional action to configure the <see cref="ClientWebSocketOptions"/> before connecting.</param>
/// <returns>The current <see cref="BiDiOptionsBuilder"/> instance for chaining.</returns>
public BiDiOptionsBuilder UseWebSocket(Action<ClientWebSocketOptions>? configure = null)
public BiDiOptionsBuilder UseWebSocket(string url, Action<ClientWebSocketOptions>? configure = null)
{
TransportFactory = (uri, ct) => WebSocketTransport.ConnectAsync(uri, configure, ct);
var uri = new Uri(url);
_transportFactory = ct => WebSocketTransport.ConnectAsync(uri, configure, ct);
return this;
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

UseWebSocket changed from an optional-configure overload to requiring a URL string, which is a breaking change for public API consumers and conflicts with the repo’s compatibility/deprecation policy (AGENTS.md:11-14, 65-67). Consider keeping the old overload (e.g., for configuring ClientWebSocketOptions) and adding the new URL-taking overload, marking the old one [Obsolete] if you want to steer usage.

Copilot uses AI. Check for mistakes.
@nvborisenko nvborisenko marked this pull request as draft March 11, 2026 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-dotnet .NET Bindings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants