Skip to content

[dotnet] Add examples for BiDi W3C Browsing Context#2735

Merged
diemol merged 8 commits into
trunkfrom
dotnet-bidi-browsingcontext-fixed
Jul 22, 2026
Merged

[dotnet] Add examples for BiDi W3C Browsing Context#2735
diemol merged 8 commits into
trunkfrom
dotnet-bidi-browsingcontext-fixed

Conversation

@diemol

@diemol diemol commented Jul 20, 2026

Copy link
Copy Markdown
Member

Description

Adds .NET examples for the BiDi Browsing Context module: creating, closing, navigating, and traversing history for browsing contexts, capturing screenshots, printing, setting the viewport, handling user prompts, and subscribing to browsing-context events. Also wires these examples into the webdriver/bidi/w3c/browsing_context docs (a CSharp tab in every code sample, across en, ja, pt-br, and zh-cn — the translated pages mirror the English page's tabpane structure exactly, so the same references apply to each).

Credit to @nvborisenko for the original work in #1940. That PR was written against a pre-release shape of the .NET BiDi API (OpenQA.Selenium.BiDi.Modules.BrowsingContext) that has since been restructured upstream, so it no longer compiles. This PR carries the same set of examples updated for the current OpenQA.Selenium.BiDi.BrowsingContext API (Selenium.WebDriver 4.46.0): flattened namespace, ImmutableArray-backed results, and IEventSource<T>.SubscribeAsync instead of OnXAsync event methods. It also:

  • Fixes a syntax error in BaseTest.cs (comma instead of semicolon).
  • Moves options.UseWebSocketUrl = true out of the version-gated branch in BaseTest.cs, since BaseChromeTest calls StartDriver() with no version and the flag would otherwise never be set, so BiDi would never actually be enabled.
  • Fixes two timing races found only by actually running the tests against real Chrome/Firefox rather than just compiling them: HandleUserPrompt needed UnhandledPromptBehavior.Ignore on Firefox so the BiDi call could handle the prompt instead of the driver auto-dismissing it, and NavigateForward/TraverseHistory needed to poll for the URL since TraverseHistoryAsync doesn't block until the navigation completes.

Note: gh-codeblock resolves against the GitHub API for the trunk branch, so the new doc references will only render real code once this PR itself is merged (a local ./build-site.sh against an unmerged branch can't verify that content, only that the shortcode syntax and paths are well-formed — which I did verify, including brace-matching every referenced line range against the actual method bodies).

Motivation and Context

#1940 has been open since September 2024 and no longer builds against the current Selenium.WebDriver release. This PR gets the same example coverage into a mergeable, passing state, wires it into the docs, and does all of this without touching the original PR.

Types of changes

  • Change to the site (I have double-checked the Netlify deployment, and my changes look good)
  • Code example added (and I also added the example to all translated languages)

Checklist

  • I have read the contributing document.
  • I have used hugo to render the site/docs locally and I am sure it works.

All 31 tests in examples/dotnet/SeleniumDocs/BiDi/BrowsingContext pass locally against Selenium.WebDriver 4.46.0 (verified twice to rule out flakiness). ./build-site.sh builds cleanly with Hugo 0.148.2 (matching README.md's recommended version) across all 4 locales.

🤖 Generated with Claude Code

Adds .NET examples for the BiDi Browsing Context module: creating,
closing, navigating, and traversing history for browsing contexts,
capturing screenshots, printing, setting the viewport, handling user
prompts, and subscribing to browsing-context events.

Credit to @nvborisenko for the original work in #1940. That PR was
written against a pre-release shape of the .NET BiDi API
(OpenQA.Selenium.BiDi.Modules.BrowsingContext) that has since been
restructured upstream, so it no longer builds. This PR carries the
same examples updated for the current OpenQA.Selenium.BiDi.BrowsingContext
API (Selenium.WebDriver 4.46.0): flattened namespace, ImmutableArray-backed
results, and IEventSource<T>.SubscribeAsync instead of OnXAsync event
methods. It also fixes a syntax error in BaseTest.cs, enables
UseWebSocketUrl unconditionally so BiDi actually works via
BaseChromeTest, and fixes two timing races (HandleUserPrompt,
NavigateForward/TraverseHistory) found by running the tests against
real Chrome/Firefox rather than just compiling them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Add .NET BiDi BrowsingContext examples and enable WebSocket BiDi mode

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Enable Chrome WebSocket URL so .NET BiDi examples can connect reliably.
• Add MSTest examples for BiDi BrowsingContext commands: create, navigate, screenshot, print.
• Add event-subscription examples using SubscribeAsync for browsing-context lifecycle and navigation
 events.
Diagram

graph TD
  A[Test runner] --> B["BaseTest.cs"] --> C[ChromeOptions]
  A --> D["BrowsingContextTest (partial)"] --> E[BiDi session]
  E --> F[BrowsingContext API]
  F --> G[Commands]
  F --> H[Events]

  subgraph Legend
    direction LR
    _t([Test/Example]) ~~~ _cfg[Config] ~~~ _api([API/Module])
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Single-file example suite
  • ➕ Easier to read top-to-bottom without jumping across partial class files
  • ➕ Less file churn when APIs change
  • ➖ Becomes a large monolithic file as modules grow
  • ➖ Harder to link individual snippets to docs later
2. Shared helpers for event/polling patterns
  • ➕ Reduces repeated TaskCompletionSource/WaitAsync boilerplate
  • ➕ Centralizes timeouts/polling defaults for less flakiness
  • ➖ May obscure the minimal API usage intended for documentation examples
  • ➖ Adds indirection that can make snippets less copy/paste friendly

Recommendation: The current approach (small, topic-focused partial test files) is a good fit for documentation-style examples and future snippet extraction. Consider adding a tiny shared helper (optional) only for the repeated event subscription + timeout pattern if flakiness becomes an issue, but keep the examples mostly “direct API calls” for clarity.

Files changed (19) +558 / -0

Tests (18) +557 / -0
BrowsingContextTest.csAdd BrowsingContext test fixture and BiDi initialization +21/-0

Add BrowsingContext test fixture and BiDi initialization

• Introduces the MSTest fixture (partial class) for BrowsingContext examples. Initializes a BiDi session and captures the top-level browsing context from GetTreeAsync() for reuse across tests.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.cs

BrowsingContextTest.Activate.csAdd example for activating a browsing context +13/-0

Add example for activating a browsing context

• Adds a test that calls context.ActivateAsync() to bring the context to the foreground. Serves as a minimal example of the activation command.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Activate.cs

BrowsingContextTest.CaptureScreenshot.csAdd screenshot examples (full, viewport, element clip) +40/-0

Add screenshot examples (full, viewport, element clip)

• Adds examples for CaptureScreenshotAsync including default screenshots and clipped screenshots via box and element clip rectangles. Validates returned data is present and convertible to bytes.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.CaptureScreenshot.cs

BrowsingContextTest.Close.csAdd examples for closing tab and window contexts +24/-0

Add examples for closing tab and window contexts

• Demonstrates creating new tab/window contexts via BiDi and closing them via context.CloseAsync(). Covers both ContextType.Tab and ContextType.Window cases.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Close.cs

BrowsingContextTest.Create.csAdd examples for creating and discovering browsing contexts +57/-0

Add examples for creating and discovering browsing contexts

• Adds examples for creating tab/window contexts (including using a reference context) and for retrieving an existing context via GetTreeAsync(). Demonstrates the primary entrypoints for obtaining BrowsingContext handles.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Create.cs

BrowsingContextTest.Event.BrowsingContextCreated.csAdd ContextCreated event subscription example +24/-0

Add ContextCreated event subscription example

• Subscribes to bidi.BrowsingContext.ContextCreated via SubscribeAsync and triggers a new window via WebDriver. Uses a TaskCompletionSource with timeout to assert an event is received.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.BrowsingContextCreated.cs

BrowsingContextTest.Event.BrowsingContextDestroyed.csAdd ContextDestroyed event subscription example +25/-0

Add ContextDestroyed event subscription example

• Subscribes to bidi.BrowsingContext.ContextDestroyed and closes the current context to trigger the event. Asserts the event’s context matches the one that was closed.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.BrowsingContextDestroyed.cs

BrowsingContextTest.Event.BrowsingContextLoaded.csAdd Load event subscription example +24/-0

Add Load event subscription example

• Subscribes to context.Load and navigates with Wait=Complete to trigger the event. Uses timeout-based awaiting to keep the example deterministic.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.BrowsingContextLoaded.cs

BrowsingContextTest.Event.DomContentLoaded.csAdd DomContentLoaded event subscription example +24/-0

Add DomContentLoaded event subscription example

• Subscribes to context.DomContentLoaded and navigates to a known page to trigger the event. Awaits the resulting event args with a bounded timeout.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.DomContentLoaded.cs

BrowsingContextTest.Event.FragmentNavigated.csAdd fragment navigation event example +26/-0

Add fragment navigation event example

• Navigates to a page, subscribes to FragmentNavigated, then navigates to the same page with a hash fragment. Validates the fragment navigation event is emitted.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.FragmentNavigated.cs

BrowsingContextTest.Event.NavigationStarted.csAdd NavigationStarted event subscription example +23/-0

Add NavigationStarted event subscription example

• Subscribes to context.NavigationStarted and triggers navigation to a test page. Asserts that navigation-start event args are received within a timeout.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.NavigationStarted.cs

BrowsingContextTest.Event.UserPrompt.csAdd user-prompt open/close event examples +46/-0

Add user-prompt open/close event examples

• Subscribes to BiDi user prompt events (opened/closed) and triggers a prompt via page interaction. Demonstrates SubscribeAsync usage for prompt-related events with timeouts.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.UserPrompt.cs

BrowsingContextTest.GetTree.csAdd GetTree examples (depth and top-level contexts) +43/-0

Add GetTree examples (depth and top-level contexts)

• Demonstrates retrieving the browsing context tree, validating iframe children, and limiting depth via MaxDepth. Also shows getting all top-level contexts after opening a new window.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.GetTree.cs

BrowsingContextTest.HandleUserPrompt.csAdd HandleUserPrompt example using Firefox BiDi session +35/-0

Add HandleUserPrompt example using Firefox BiDi session

• Creates a FirefoxDriver configured for WebSocket BiDi and prompt handling, then subscribes to UserPromptOpened and handles the prompt via context.HandleUserPromptAsync(). This provides a working end-to-end example of prompt interaction via BiDi.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.HandleUserPrompt.cs

BrowsingContextTest.Navigate.csAdd navigation and history traversal examples with URL polling +81/-0

Add navigation and history traversal examples with URL polling

• Adds examples for NavigateAsync (with and without readiness state) and for TraverseHistoryAsync back/forward. Includes a helper that polls GetTreeAsync() until the expected URL is observed to avoid timing races.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Navigate.cs

BrowsingContextTest.Print.csAdd PrintAsync example with page ranges +17/-0

Add PrintAsync example with page ranges

• Demonstrates printing via context.PrintAsync with a variety of page range syntaxes. Asserts the returned PDF data is present and convertible to bytes.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Print.cs

BrowsingContextTest.Reload.csAdd ReloadAsync example +20/-0

Add ReloadAsync example

• Navigates to a known page and calls context.ReloadAsync(), asserting navigation info is returned. Provides a minimal reload example for the module.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Reload.cs

BrowsingContextTest.SetViewport.csAdd viewport configuration example +14/-0

Add viewport configuration example

• Demonstrates setting viewport size and device pixel ratio via context.SetViewportAsync(). Serves as a minimal example of viewport control via BiDi.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.SetViewport.cs

Other (1) +1 / -0
BaseTest.csEnable WebSocket URL for Chrome driver startup +1/-0

Enable WebSocket URL for Chrome driver startup

• Sets ChromeOptions.UseWebSocketUrl = true unconditionally when starting the driver. This ensures BiDi connectivity is enabled for tests that rely on driver.AsBiDiAsync().

examples/dotnet/SeleniumDocs/BaseTest.cs

@netlify

netlify Bot commented Jul 20, 2026

Copy link
Copy Markdown

Deploy Preview for selenium-dev ready!

Name Link
🔨 Latest commit 39ff8cb
🔍 Latest deploy log https://app.netlify.com/projects/selenium-dev/deploys/6a60bbdcc7f4360008ba2b21
😎 Deploy Preview https://deploy-preview-2735--selenium-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@qodo-code-review

qodo-code-review Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

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

Context used
✅ Compliance rules (platform): 10 rules

Grey Divider


Action required

1. Partial accessibility mismatch 🐞 Bug ≡ Correctness
Description
BrowsingContextTest is declared public in one file but the new partial declarations omit an
access modifier (defaulting to internal), which causes a compile-time error due to inconsistent
partial type accessibility.
Code

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Activate.cs[R6-7]

+partial class BrowsingContextTest
+{
Evidence
The primary test class is explicitly public, while the other partial declarations are implicitly
internal due to missing modifiers, which is not allowed for partial type declarations.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.cs[7-10]
examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Activate.cs[4-7]
examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Print.cs[4-7]

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

## Issue description
`BrowsingContextTest` is declared as `public partial` in `BrowsingContextTest.cs`, but all the other new `partial class BrowsingContextTest` declarations omit an access modifier (implicitly `internal`). C# requires all partial declarations of a type to have compatible accessibility, so this currently fails compilation.

## Issue Context
This affects every newly-added `BrowsingContextTest.*.cs` partial file.

## Fix Focus Areas
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.cs[7-9]
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Activate.cs[4-7]

(Apply the same `public partial class BrowsingContextTest` modifier to all other `BrowsingContextTest.*.cs` files in this folder.)

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



Remediation recommended

2. Print uses unrealistic ranges 🐞 Bug ☼ Reliability ⭐ New
Description
PrintPage prints the initial about:blank browsing context and requests multiple page ranges
(including ranges beyond page 1), which can cause PrintAsync to throw when the printed document
does not have those pages.
Code

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Print.cs[R9-15]

+    public async Task PrintPage()
+    {
+        var pdf = await context.PrintAsync(new() { PageRanges = [1, 2, 3..5, new(3, 5), 7..] });
+
+        Assert.IsNotNull(pdf);
+        Assert.IsNotNull(pdf.Data);
+        Assert.IsNotNull(pdf.ToByteArray());
Evidence
The C# print example prints immediately with complex ranges, while test initialization does not
navigate the context to a page; the existing Java example navigates to a page before printing and
uses default options, demonstrating a more deterministic setup.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Print.cs[8-16]
examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.cs[14-20]
examples/java/src/test/java/dev/selenium/bidirectional/webdriver_bidi/BrowsingContextTest.java[304-314]

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

### Issue description
`BrowsingContextTest.PrintPage` calls `context.PrintAsync(...)` without first navigating to a deterministic page, and it requests page ranges that are likely invalid for the initial document (often a single-page `about:blank`). This can cause print to fail depending on how the browser/driver validates page ranges.

### Issue Context
`InitializeBidi` only attaches to the existing top-level context; it does not navigate it to content before tests like `PrintPage` run.

### Fix Focus Areas
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Print.cs[8-16]
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.cs[14-20]

### Suggested fix
- Navigate to a known page before printing (e.g., `await context.NavigateAsync(..., new(){ Wait = ReadinessState.Complete })`).
- Use default print options (no `PageRanges`) or constrain to a known-valid range like `[1]` unless the example explicitly demonstrates multi-page printing.

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


3. Assumes nonempty context tree 🐞 Bug ☼ Reliability
Description
BrowsingContextTest indexes Contexts[0] from GetTreeAsync() without checking Contexts.Length,
which can throw IndexOutOfRangeException and obscure the real setup/navigation failure when the
response is unexpectedly empty. This occurs both during test initialization and during URL polling.
Code

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.cs[19]

+        context = (await bidi.BrowsingContext.GetTreeAsync()).Contexts[0].Context;
Evidence
Both the test setup and the URL polling helper directly index Contexts[0] from a GetTreeAsync()
result without checking whether any contexts were returned, which is a direct
IndexOutOfRangeException risk if the returned array is empty.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.cs[15-20]
examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Navigate.cs[56-63]

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

### Issue description
`GetTreeAsync()` results are indexed with `Contexts[0]` without validating the array is non-empty. If the tree is unexpectedly empty, the tests fail with `IndexOutOfRangeException` rather than a descriptive failure.

### Issue Context
This happens in test initialization (choosing the baseline `context`) and in `WaitForUrlAsync` (polling the current URL), so the failure mode can appear both at startup and during navigation history examples.

### Fix Focus Areas
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.cs[15-20]
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Navigate.cs[56-63]

### Suggested fix
- Store the tree result in a local variable and `Assert.IsTrue(tree.Contexts.Length > 0, "...")` (or throw a descriptive exception) before indexing.
- In `WaitForUrlAsync`, similarly guard against empty `Contexts` and fail with a message that includes the last observed state.

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


4. Closes last context 🐞 Bug ☼ Reliability
Description
BrowsingContextDestroyedEvent closes context, which is initialized to the first top-level
browsing context in the session; if that is the only window, behavior after closing becomes
browser/driver-dependent and can lead to intermittent timeouts or cleanup failures. The repo’s
existing destroyed-event examples keep the session alive by creating and closing a separate
window/tab instead.
Code

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.BrowsingContextDestroyed.cs[R15-19]

+        await bidi.BrowsingContext.ContextDestroyed.SubscribeAsync(args => tcs.TrySetResult(args));
+
+        await context.CloseAsync();
+
+        var contextInfo = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
Evidence
The test’s context field is set from the first entry of bidi.BrowsingContext.GetTreeAsync(), so
BrowsingContextDestroyedEvent is closing the primary/only context for a fresh session. In
contrast, the repo’s Java destroyed-event example creates a new window and closes it before awaiting
the destroyed event, keeping the original context alive during the wait.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.cs[14-20]
examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.BrowsingContextDestroyed.cs[10-23]
examples/java/src/test/java/dev/selenium/bidirectional/webdriver_bidi/BrowsingContextInspectorTest.java[169-185]

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

## Issue description
`BrowsingContextDestroyedEvent` currently calls `await context.CloseAsync()` on the default session context (initialized from `GetTreeAsync().Contexts[0]`). If that is the only top-level browsing context, closing it can make subsequent behavior (including event delivery timing and test cleanup) browser/driver-dependent.

## Issue Context
Other destroyed-event examples in this repo trigger the event by creating a *new* window and closing that window, leaving the original context open.

## Fix Focus Areas
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.BrowsingContextDestroyed.cs[10-23]
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.cs[14-20]

## Suggested change
Within `BrowsingContextDestroyedEvent`, create a new browsing context (tab/window), subscribe to `ContextDestroyed`, close **that newly created context**, and assert the event corresponds to it (instead of closing the session’s primary `context`).

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


View more (4)
5. Prompt close is implicit ✓ Resolved 🐞 Bug ☼ Reliability
Description
UserPromptClosedEvent waits for UserPromptClosed after triggering a JS prompt but never
explicitly accepts/dismisses it, so the event depends on Chrome/driver auto-handling and can time
out under different prompt-handling behavior. This also weakens the example by not demonstrating a
deterministic “close” lifecycle.
Code

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.UserPrompt.cs[R30-41]

+    public async Task UserPromptClosedEvent()
+    {
+        TaskCompletionSource<UserPromptClosedEventArgs> tcs = new();
+
+        await context.NavigateAsync("https://www.selenium.dev/selenium/web/alerts.html", new() { Wait = ReadinessState.Complete });
+
+        // TODO: this event can be a part of context
+        await bidi.BrowsingContext.UserPromptClosed.SubscribeAsync(args => tcs.TrySetResult(args));
+
+        driver.FindElement(By.Id("prompt")).Click();
+
+        var userPromptClosedEventArgs = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
Evidence
The UserPromptClosedEvent test subscribes to the closed event and clicks a prompt-triggering
element, but contains no explicit accept/dismiss call (BiDi or classic alert handling), so
WaitAsync will only complete if something else closes the prompt. The test class is Chrome-based
by default, and the separate HandleUserPrompt example explicitly documents relying on Chrome
auto-handling prompts, reinforcing that this closed-event example is currently
implicit/behavior-dependent.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.UserPrompt.cs[30-45]
examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.cs[7-20]
examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.HandleUserPrompt.cs[14-18]

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

## Issue description
`UserPromptClosedEvent` triggers a prompt and then awaits the `UserPromptClosed` event without any explicit action to close the prompt. This makes the example reliant on browser/driver defaults (e.g., Chrome auto-dismiss), which can turn into a timeout/flaky test and is not a deterministic demonstration.

## Issue Context
The class runs under Chrome by default (`BrowsingContextTest : BaseChromeTest`), while another example explicitly notes Chrome auto-handles prompts.

## Fix Focus Areas
- Add deterministic prompt close (e.g., subscribe to `UserPromptOpened`, then call `context.HandleUserPromptAsync(...)`, then await `UserPromptClosed`).
- Keep the example robust if Chrome auto-closes (e.g., tolerate “already closed” errors if needed).

### Code references
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.UserPrompt.cs[30-45]
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.cs[7-20]
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.HandleUserPrompt.cs[14-18]

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


6. Two browsers started 🐞 Bug ☼ Reliability
Description
HandleUserPrompt creates a FirefoxDriver inside a class that always auto-starts a Chrome driver
via BaseChromeTest, so the test requires both browsers and starts two browser sessions per run.
Code

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.HandleUserPrompt.cs[R16-18]

+        // temporary use firefox because of chrome automatically handle prompts
+        using var driver = new FirefoxDriver(new FirefoxOptions() { UseWebSocketUrl = true, UnhandledPromptBehavior = UnhandledPromptBehavior.Ignore });
+
Evidence
The base class unconditionally starts Chrome for every test, while HandleUserPrompt additionally
creates a Firefox driver, resulting in two browser sessions for the same test.

examples/dotnet/SeleniumDocs/BaseChromeTest.cs[6-13]
examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.HandleUserPrompt.cs[14-23]
examples/dotnet/SeleniumDocs/BaseTest.cs[25-34]

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

## Issue description
`BrowsingContextTest` derives from `BaseChromeTest`, which always starts Chrome in `[TestInitialize]`. `HandleUserPrompt` then starts Firefox in the test body. This makes the single test depend on both Chrome and Firefox availability and adds unnecessary overhead.

## Issue Context
The base `Cleanup` will quit only the inherited Chrome `driver` field; the Firefox instance is separately disposed via `using`, so both browsers are active concurrently.

## Fix Focus Areas
- examples/dotnet/SeleniumDocs/BaseChromeTest.cs[6-13]
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.HandleUserPrompt.cs[13-34]

## Suggested change
Move `HandleUserPrompt` into a separate `[TestClass]` that derives from `BaseTest` or `BaseFirefoxTest` (and does not inherit the Chrome auto-start), so only Firefox is started for that test.

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


7. Non-idempotent event completion ✓ Resolved 🐞 Bug ☼ Reliability
Description
The event examples pass TaskCompletionSource.SetResult directly to SubscribeAsync; if multiple
matching events are delivered during the subscription window, SetResult will throw and can break
the test run.
Code

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.NavigationStarted.cs[R13-16]

+        TaskCompletionSource<NavigationStartedEventArgs> tcs = new();
+
+        await context.NavigationStarted.SubscribeAsync(tcs.SetResult);
+
Evidence
The code wires BiDi events to TaskCompletionSource.SetResult without guarding against repeated
invocations of the handler.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.NavigationStarted.cs[11-17]
examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.BrowsingContextCreated.cs[13-17]

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

## Issue description
Several event tests subscribe with `SubscribeAsync(tcs.SetResult)`. `SetResult` is not safe if the callback can be invoked more than once; a second invocation throws and can surface as intermittent failures.

## Issue Context
This pattern appears across multiple event tests (NavigationStarted, Load, DomContentLoaded, ContextCreated/Destroyed, UserPrompt*).

## Fix Focus Areas
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Event.NavigationStarted.cs[13-16]

(Replicate the same fix in the other `BrowsingContextTest.Event.*.cs` files using the same subscription pattern.)

## Suggested change
Replace `tcs.SetResult` with an idempotent handler, e.g.:
```csharp
await context.NavigationStarted.SubscribeAsync(args => tcs.TrySetResult(args));
```
Optionally, if `SubscribeAsync` returns a subscription/disposable, dispose/unsubscribe after the first completion.

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


8. BaseTest.cs gh-codeblock line drift ✓ Resolved 📘 Rule violation ≡ Correctness
Description
The PR adds a line in examples/dotnet/SeleniumDocs/BaseTest.cs, but docs still reference
BaseTest.cs#L48, which now points to } instead of the intended driver-start line. This causes
the rendered documentation snippet to be incorrect/out-of-date.
Code

examples/dotnet/SeleniumDocs/BaseTest.cs[39]

+            options.UseWebSocketUrl = true;
Evidence
The compliance rule requires updating gh-codeblock line references when example files change. After
the new line in BaseTest.cs, line 48 is now } while the driver initialization moved to line
49, but the docs still reference #L48.

Rule 2141349: Update gh-codeblock line ranges when examples change
website_and_docs/content/documentation/webdriver/drivers/_index.en.md[30-40]
examples/dotnet/SeleniumDocs/BaseTest.cs[36-50]

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

## Issue description
Docs reference `BaseTest.cs#L48`, but this PR inserted a new line above, shifting the intended snippet line(s). As a result, the gh-codeblock now renders the wrong line (`}`) instead of the intended code.

## Issue Context
`website_and_docs/content/documentation/webdriver/drivers/_index.*.md` uses gh-codeblock pointers into `examples/dotnet/SeleniumDocs/BaseTest.cs`. After adding `options.UseWebSocketUrl = true;`, the referenced line number should be updated.

## Fix Focus Areas
- examples/dotnet/SeleniumDocs/BaseTest.cs[36-50]
- website_and_docs/content/documentation/webdriver/drivers/_index.en.md[30-40]
- website_and_docs/content/documentation/webdriver/drivers/_index.ja.md[30-40]
- website_and_docs/content/documentation/webdriver/drivers/_index.pt-br.md[30-40]
- website_and_docs/content/documentation/webdriver/drivers/_index.zh-cn.md[30-40]

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



Informational

9. URL wait lacks timeout signal 🐞 Bug ◔ Observability
Description
WaitForUrlAsync has a deadline but returns the last observed URL on expiry instead of signaling a
timeout, so callers only see a generic assertion mismatch rather than an explicit wait failure. This
makes navigation/timing problems harder to diagnose from test output.
Code

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Navigate.cs[R56-68]

+    private async Task<string> WaitForUrlAsync(string expectedUrl)
+    {
+        var deadline = DateTime.UtcNow.AddSeconds(5);
+        string url;
+        do
+        {
+            url = (await context.GetTreeAsync()).Contexts[0].Url;
+            if (url == expectedUrl) return url;
+            await Task.Delay(100);
+        } while (DateTime.UtcNow < deadline);
+
+        return url;
+    }
Evidence
The helper computes a deadline and polls, but after the deadline it executes return url; rather
than throwing/indicating a timeout condition.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Navigate.cs[56-68]

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

### Issue description
`WaitForUrlAsync` polls until a deadline, but on expiry it returns normally with the last observed URL. That makes timeouts indistinguishable from “wrong URL” failures and produces less actionable test output.

### Issue Context
This helper is used by `NavigateBack`, `NavigateForward`, and `TraverseHistory`.

### Fix Focus Areas
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.Navigate.cs[56-68]

### Suggested fix
- After the loop, throw a `TimeoutException` (or assert-fail) that includes both the expected URL and the last observed URL.
- (Optional) Accept a timeout parameter to make the helper reusable across environments.

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


10. Unchecked node indexing ✓ Resolved 🐞 Bug ☼ Reliability
Description
CaptureElementScreenshot indexes Nodes[0] from LocateNodesAsync without validating that any
nodes were returned, which can throw IndexOutOfRangeException and produce a poor failure mode if
the locator/page changes.
Code

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.CaptureScreenshot.cs[R31-35]

+        driver.Url = "https://www.selenium.dev/selenium/web/formPage.html";
+
+        var element = (await context.LocateNodesAsync(new CssLocator("#checky"))).Nodes[0];
+
+        var screenshot = await context.CaptureScreenshotAsync(new() { Clip = new ElementClipRectangle(element) });
Evidence
The code directly accesses Nodes[0] with no guard, so an empty result will throw immediately.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.CaptureScreenshot.cs[28-35]

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 test assumes `LocateNodesAsync` always finds `#checky` and immediately indexes `Nodes[0]`. If the node list is empty, the test throws before producing a clear assertion message.

## Issue Context
This is an example test; a clearer assert makes failures easier to diagnose when the page/locator changes.

## Fix Focus Areas
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.CaptureScreenshot.cs[31-35]

## Suggested change
Capture the nodes result and assert it is non-empty before indexing, e.g.:
```csharp
var result = await context.LocateNodesAsync(new CssLocator("#checky"));
Assert.IsTrue(result.Nodes.Length > 0, "Expected to locate #checky");
var element = result.Nodes[0];
```

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


11. Assumed context ordering ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
GetAllTopLevelBrowsingContexts assumes the newly created window will be at contexts[1], which is
a brittle positional assertion and can fail if the returned context array ordering differs.
Code

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.GetTree.cs[R34-42]

+    public async Task GetAllTopLevelBrowsingContexts()
+    {
+        var window = (await bidi.BrowsingContext.CreateAsync(ContextType.Window)).Context;
+
+        var contexts = (await bidi.BrowsingContext.GetTreeAsync()).Contexts;
+
+        Assert.AreEqual(2, contexts.Length);
+        Assert.AreEqual(contexts[1].Context, window);
+    }
Evidence
The test currently relies on contexts[1] to refer to the created window without validating the
ordering contract for the returned array.

examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.GetTree.cs[33-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
The test asserts `contexts[1].Context == window`, assuming a specific ordering from `GetTreeAsync()`. If ordering changes, the test fails despite correct behavior.

## Issue Context
A more robust assertion is to verify that the created `window` exists somewhere in the returned contexts.

## Fix Focus Areas
- examples/dotnet/SeleniumDocs/BiDi/BrowsingContext/BrowsingContextTest.GetTree.cs[34-42]

## Suggested change
Replace the positional assertion with a containment check, e.g.:
```csharp
Assert.IsTrue(contexts.Any(c => c.Context == window));
```

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


Grey Divider

Qodo Logo

Comment thread examples/dotnet/SeleniumDocs/BaseTest.cs
…ales)

Adds a CSharp tab, referencing the new examples/dotnet/SeleniumDocs/BiDi/
BrowsingContext example files, to every code tabpane in
webdriver/bidi/w3c/browsing_context across en, ja, pt-br, and zh-cn. These
locales mirror the English page's tabpane structure exactly, so the same
gh-codeblock references apply to each. Line ranges were derived by
brace-matching each referenced method body and validated against the
current file contents, matching the file's existing convention of pointing
at just the illustrative body of each method (excluding the signature and
braces).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 2cdf2b0

Fixes real issues flagged by Qodo's automated review, verified against the
current commit before applying:

- Fix a gh-codeblock line reference broken by this PR's own BaseTest.cs
  edit: adding `options.UseWebSocketUrl = true;` shifted every subsequent
  line by one, so the four drivers/_index.*.md pages that reference
  BaseTest.cs#L48 (the "driver = new ChromeDriver(options);" line) were
  pointing at a lone closing brace. Updated all four locales to #L49.
- Make the BiDi event-subscription examples idempotent: SubscribeAsync
  handlers used TaskCompletionSource.SetResult, which throws if the same
  event fires more than once during the subscription window. Switched to
  TrySetResult (wrapped in a lambda, since TrySetResult's bool return isn't
  method-group-compatible with the Action<T> the API expects).
- Guard CaptureElementScreenshot's node lookup with an assertion before
  indexing, so a failed locator produces a clear failure message instead of
  an IndexOutOfRangeException.
- Replace the positional `contexts[1]` assertion in
  GetAllTopLevelBrowsingContexts with a containment check, since
  GetTreeAsync's ordering isn't a documented contract.

Updated the browsing_context.*.md gh-codeblock line ranges for the two
files whose line counts changed as a result (CaptureScreenshot.cs,
GetTree.cs), across all four locales, and re-verified every dotnet
gh-codeblock reference site-wide (736 total) against the actual file
contents.

Qodo also flagged a "partial accessibility mismatch" (public vs internal
partial class declarations) as a compile error requiring action. This is
incorrect for this code: `dotnet build` succeeds and all 31 tests pass
locally, both before and after this commit; C# allows secondary partial
declarations to omit the access modifier without conflict. Not changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 550631c

Qodo's re-review (after the previous fix commit) flagged that
UserPromptClosedEvent waits for the closed event without ever explicitly
closing the prompt, calling it implicit/behavior-dependent.

Tried making it deterministic by explicitly calling HandleUserPromptAsync
after waiting for UserPromptOpened. That actually broke the test:
OpenQA.Selenium.BiDi.BiDiException: no such alert: No dialog is showing.
Chrome's default unhandledPromptBehavior ("dismiss and notify") already
closes the prompt on its own, faster than an explicit BiDi call can act on
it. This is a known, deliberate constraint in this codebase already:
HandleUserPrompt.cs uses Firefox specifically ("temporary use firefox
because of chrome automatically handle prompts") whenever the example
needs to demonstrate an explicit close.

Reverted the explicit-close attempt and documented the actual mechanism in
a comment instead: Chrome's own auto-dismiss is what raises the event
here, with a pointer to HandleUserPrompt.cs for the explicit-close
pattern. Verified the full 31-test suite passes with this change, and
updated the shifted gh-codeblock line reference in all four locales.

Qodo's other two remaining findings on this PR were addressed in the
previous commit's message: "Partial accessibility mismatch" is a false
positive (dotnet build and all tests pass with this exact partial-class
pattern), and "Two browsers started" is a pre-existing structural choice
from the original PR (#1940), not introduced here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 1d4d679

Implements Qodo's suggested remediation in full this time: subscribe to
UserPromptOpened, wait for it, then explicitly call HandleUserPromptAsync,
tolerating the case where Chrome's default unhandled-prompt behavior has
already closed the dialog (BiDiException: "no such alert"). This
demonstrates the explicit-close pattern while staying robust under
Chrome's race, verified by running the test 3x plus the full 31-test suite
with no flakiness.

BiDiException carries no structured error code (just a message), so the
catch matches on message text rather than a dedicated exception type.

Adding `using OpenQA.Selenium.BiDi;` (needed for BiDiException) shifted
every line in the file by one, so both UserPromptOpenedEvent and
UserPromptClosedEvent's gh-codeblock references needed updating, across
all four locales.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 5976832

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit cb2c9ee

Matches the Java, Python, and Ruby examples, which all create a dedicated
temp subdirectory (Files.createTempDirectory, tempfile.TemporaryDirectory,
Dir.mktmpdir) rather than passing the shared OS temp root itself as
--profile-root. The shared root is the likely cause of geckodriver exiting
immediately on Windows CI (consistently failing on trunk's own scheduled
runs, unrelated to any specific PR). Also reorders Cleanup() to quit the
driver before deleting the directory, since deleting while Firefox still
holds files open in it raced intermittently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit cd561d6

…y listing

The previous fix still failed on Windows CI with the identical crash
("Driver service process exited unexpectedly before initialization
completed"), and every other FirefoxDriver launch in the same run
succeeded, isolating the problem to the ProfileRoot value itself rather
than to sharing vs. not sharing the OS temp root. Both the original code
and the previous fix left a trailing separator on that path (Windows'
Path.GetTempPath() already ends in one, and the prior fix re-added one
explicitly), which is a known Windows command-line quoting hazard when a
backslash immediately precedes a closing quote.

Stop building a path with a trailing separator, and switch the assertion
to list GetTempDirectory()'s contents and check the profile's leaf
directory name is among them, mirroring Python's
`profile_name in os.listdir(temp_dir)` and Ruby's substring-based check
instead of exact string reconstruction that assumed a specific
separator style.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 39ff8cb

@diemol
diemol merged commit 64ad0a5 into trunk Jul 22, 2026
10 checks passed
@diemol
diemol deleted the dotnet-bidi-browsingcontext-fixed branch July 22, 2026 13:48
selenium-ci added a commit that referenced this pull request Jul 22, 2026
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.

1 participant