Skip to content

[dotnet] Start ChromeDriver asynchronously#17112

Draft
nvborisenko wants to merge 4 commits intoSeleniumHQ:trunkfrom
nvborisenko:dotnet-start-async-chrome
Draft

[dotnet] Start ChromeDriver asynchronously#17112
nvborisenko wants to merge 4 commits intoSeleniumHQ:trunkfrom
nvborisenko:dotnet-start-async-chrome

Conversation

@nvborisenko
Copy link
Member

var chrome = await ChromeDriver.StartAsync();

// or

var chrome = await ChromeDriver.StartAsync(new() { BrowserVersion = "145" });

🔗 Related Issues

Contributes to #17086 and #14067

💥 What does this PR do?

Introduces asynchronous initialization support for the ChromeDriver and ChromiumDriver classes, refactors driver session startup logic to allow optional automatic session creation, and improves code clarity and safety with better nullability handling and modern C# syntax. The changes also add comprehensive documentation for new and existing APIs.

💡 Additional Considerations

Only overload which takes ChromeOptions. Others will come soon?

🔄 Types of changes

  • Bug fix (backwards compatible)
  • New feature (non-breaking change which adds functionality and tests!)

Copilot AI review requested due to automatic review settings February 18, 2026 21:47
@selenium-ci selenium-ci added the C-dotnet .NET Bindings label Feb 18, 2026
@qodo-code-review
Copy link
Contributor

PR Type

Enhancement


Description

  • Introduces asynchronous initialization for ChromeDriver and ChromiumDriver

  • Adds StartAsync() static methods for non-blocking driver creation

  • Refactors WebDriver base class to support optional automatic session startup

  • Improves code safety with nullability annotations and modern C# syntax


File Walkthrough

Relevant files
Enhancement
ChromeDriver.cs
Add async initialization methods to ChromeDriver                 

dotnet/src/webdriver/Chrome/ChromeDriver.cs

  • Adds protected constructor accepting ICommandExecutor, ChromeOptions,
    and autoStartSession flag
  • Implements two StartAsync() static methods for asynchronous driver
    creation with default and custom options
  • Includes comprehensive XML documentation for new async APIs
  • Handles proper cleanup and exception propagation in async
    initialization
+55/-0   
ChromiumDriver.cs
Add async-aware constructor to ChromiumDriver                       

dotnet/src/webdriver/Chromium/ChromiumDriver.cs

  • Adds protected constructor with ICommandExecutor, ChromiumOptions, and
    autoStartSession parameters
  • Changes GenerateDriverServiceCommandExecutorAsync() from private to
    protected visibility
  • Adds comprehensive XML documentation for the new constructor and async
    method
  • Maintains proper capability name validation in new constructor
+23/-1   
WebDriver.cs
Refactor WebDriver for async session initialization           

dotnet/src/webdriver/WebDriver.cs

  • Refactors base constructor to support optional automatic session
    initialization via autoStartSession parameter
  • Implements new StartSessionAsync() method for asynchronous session
    creation
  • Updates StartSession() to delegate to async method using Task.Run()
  • Adds nullability annotations (= null!) to Capabilities and SessionId
    properties
  • Modernizes collection initialization syntax using target-typed new()
    expressions
  • Improves error handling and documentation with XML comments
+46/-21 

@qodo-code-review
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🟢
🎫 #17086
🟢 Provide a non-breaking async initialization API for .NET so users can create a driver via
await ChromeDriver.StartAsync().
Avoid requiring an async constructor; instead, offer an alternative async
creation/initialization approach.
Ensure the approach is a small incremental step toward broader async support without
painful migration for end users.
🟡
🎫 #14067
🟢 Rewrite sync methods to use the async methods.
🔴 Add async methods to all classes in the .NET bindings.
Mark all sync methods as deprecated.
Write a blog post describing the proposed changes.
Decide long-term strategy: remove sync methods in Selenium 5 vs maintain both through
Selenium 6 (and related decision dependencies).
Provide async usage patterns (example: `await driver.FindElementAsync(...)`).
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
No custom compliance provided

Follow the guide to enable custom compliance check.

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Initialize property to prevent null reference

Initialize the Capabilities property with new DesiredCapabilities() instead of
null! to prevent a potential NullReferenceException when a session is not
started automatically.

dotnet/src/webdriver/WebDriver.cs [105]

-public ICapabilities Capabilities { get; private set; } = null!;
+public ICapabilities Capabilities { get; private set; } = new DesiredCapabilities();
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential NullReferenceException when autoStartSession is false and proposes a valid fix by initializing the Capabilities property, enhancing the robustness of the class.

Medium
Use nullable type for optional property

Change the SessionId property to be nullable (SessionId?) to prevent a
NullReferenceException if it's accessed before a session is started.

dotnet/src/webdriver/WebDriver.cs [197]

-public SessionId SessionId { get; private set; } = null!;
+public SessionId? SessionId { get; private set; }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential NullReferenceException for the SessionId property when autoStartSession is false. Making the property nullable is a good practice to reflect its state accurately.

Medium
  • More

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 adds async driver initialization to the .NET bindings, enabling ChromeDriver (and related Chromium plumbing) to be started via an awaitable API while preserving existing synchronous constructors.

Changes:

  • Refactors WebDriver session creation to support optional automatic session start and introduces StartSessionAsync.
  • Adds ChromeDriver.StartAsync(...) factory methods for asynchronous driver startup.
  • Exposes async service/executor creation hooks in ChromiumDriver to support async startup flows.

Reviewed changes

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

File Description
dotnet/src/webdriver/WebDriver.cs Adds optional auto-start session behavior and introduces async session-start implementation.
dotnet/src/webdriver/Chromium/ChromiumDriver.cs Adds constructor overload to control auto session start and exposes async executor generation for derived drivers.
dotnet/src/webdriver/Chrome/ChromeDriver.cs Introduces StartAsync APIs and a constructor overload to support async startup without auto session creation.

}

ChromeDriverService service = ChromeDriverService.CreateDefaultService();
ICommandExecutor executor = await GenerateDriverServiceCommandExecutorAsync(service, options, DefaultCommandTimeout).ConfigureAwait(false);
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

StartAsync(ChromeOptions) creates a ChromeDriverService before calling GenerateDriverServiceCommandExecutorAsync(...), but if that call throws (e.g., driver/browser discovery fails before the internal try/catch that disposes the service), the newly created service instance is never disposed. Wrap executor creation in a try/catch and dispose the service on failure (prefer DisposeAsync if appropriate) to avoid leaking resources when startup fails early.

Suggested change
ICommandExecutor executor = await GenerateDriverServiceCommandExecutorAsync(service, options, DefaultCommandTimeout).ConfigureAwait(false);
ICommandExecutor executor;
try
{
executor = await GenerateDriverServiceCommandExecutorAsync(service, options, DefaultCommandTimeout).ConfigureAwait(false);
}
catch
{
service.Dispose();
throw;
}

Copilot uses AI. Check for mistakes.
Comment on lines +175 to +190
/// <summary>
/// Asynchronously creates and starts a new instance of the <see cref="ChromeDriver"/> class with default options.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains the initialized <see cref="ChromeDriver"/>.</returns>
public static Task<ChromeDriver> StartAsync()
{
return StartAsync(new ChromeOptions());
}

/// <summary>
/// Asynchronously creates and starts a new instance of the <see cref="ChromeDriver"/> class using the specified options.
/// </summary>
/// <param name="options">The <see cref="ChromeOptions"/> to be used with the Chrome driver.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the initialized <see cref="ChromeDriver"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="options"/> is <see langword="null"/>.</exception>
public static async Task<ChromeDriver> StartAsync(ChromeOptions options)
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

The new ChromeDriver.StartAsync(...) entry point is user-facing behavior, but there are no tests validating it (successful startup path and failure/disposal behavior). Please add coverage in the existing .NET test suite (e.g., under dotnet/test/chrome or dotnet/test/common) to ensure StartAsync actually creates a usable session and that resources are cleaned up on exceptions.

Copilot uses AI. Check for mistakes.
@kelmelzer
Copy link

Are you going to flag the standard constructor [Obsolete] and push everyone towards the new static factory async method?

@nvborisenko nvborisenko marked this pull request as draft February 19, 2026 15:42
@nvborisenko
Copy link
Member Author

Not sure how it should happen. My vote is to obsolete in v5 and remove in v6. Keep eyes on #14067

{
if (options is null)
{
throw new ArgumentNullException(nameof(options), "Chrome options must not be null");
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
throw new ArgumentNullException(nameof(options), "Chrome options must not be null");
throw new ArgumentNullException(nameof(options));

I would remove the custom exception message. The default one is fine.

@YevgeniyShunevych
Copy link
Contributor

Generally, looks great. I would additionally like to have a full StartAsync method overload analogous to the full constructor and cancellationToken parameter:

public static async Task<ChromeDriver> StartAsync(
    ChromeDriverService service,
    ChromeOptions options,
    TimeSpan commandTimeout,
    CancellationToken cancellationToken = default);

@nvborisenko
Copy link
Member Author

Generally, looks great. I would additionally like to have a full StartAsync method overload analogous to the full constructor and cancellationToken parameter:

public static async Task<ChromeDriver> StartAsync(
    ChromeDriverService service,
    ChromeOptions options,
    TimeSpan commandTimeout,
    CancellationToken cancellationToken = default);
  • CancellationToken - yes, definitely
  • service, already constructed, WD then disposes it later. IMHO it is bug. I should properly test what is expected behaviour and "fix" it, we have great chance.
  • commandTimeout - 50/50. Keeping in mind.

Currently I am stuck here: #17115 and #17121

@YevgeniyShunevych
Copy link
Contributor

service, already constructed, WD then disposes it later. IMHO it is bug. I should properly test what is expected behaviour and "fix" it, we have great chance.

Some users (at least me) anyway might still need ChromeDriverService service parameter to be able to configure service the way they need. We want to be able to create a service using method CreateDefaultService(string? driverPath, string? driverExecutableFileName) to pass custom driver path, as well as be able to configure different properties of DriverService, ChromiumDriverService, etc.

@nvborisenko
Copy link
Member Author

Should be covered by #17115, hopefully we will do something interesting there.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants