From 898d65dd1c706b428bd540c5b0abf93e926e4952 Mon Sep 17 00:00:00 2001
From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com>
Date: Fri, 20 Feb 2026 18:01:13 +0300
Subject: [PATCH 1/6] [dotnet] Any webdriver can be disposed asynchronously
---
dotnet/src/support/BUILD.bazel | 5 ++
.../support/Events/EventFiringWebDriver.cs | 27 +++++++
dotnet/src/webdriver/IWebDriver.cs | 2 +-
dotnet/src/webdriver/WebDriver.cs | 72 +++++++++++++++----
dotnet/test/common/StubDriver.cs | 10 +++
5 files changed, 102 insertions(+), 14 deletions(-)
diff --git a/dotnet/src/support/BUILD.bazel b/dotnet/src/support/BUILD.bazel
index b4251681c8452..19d8a5d896384 100644
--- a/dotnet/src/support/BUILD.bazel
+++ b/dotnet/src/support/BUILD.bazel
@@ -3,6 +3,7 @@ load(
"csharp_library",
"generated_assembly_info",
"nuget_pack",
+ "nuget_package",
)
load(
"//dotnet:selenium-dotnet-version.bzl",
@@ -44,6 +45,8 @@ csharp_library(
],
deps = [
"//dotnet/src/webdriver:webdriver-netstandard2.0",
+ nuget_package("Microsoft.Bcl.AsyncInterfaces"),
+ nuget_package("System.Threading.Tasks.Extensions"),
],
)
@@ -84,6 +87,8 @@ csharp_library(
],
deps = [
"//dotnet/src/webdriver:webdriver-netstandard2.0-strongnamed",
+ nuget_package("Microsoft.Bcl.AsyncInterfaces"),
+ nuget_package("System.Threading.Tasks.Extensions"),
],
)
diff --git a/dotnet/src/support/Events/EventFiringWebDriver.cs b/dotnet/src/support/Events/EventFiringWebDriver.cs
index 70b69589f7500..ee27380eec7e4 100644
--- a/dotnet/src/support/Events/EventFiringWebDriver.cs
+++ b/dotnet/src/support/Events/EventFiringWebDriver.cs
@@ -405,6 +405,17 @@ public void Dispose()
GC.SuppressFinalize(this);
}
+ ///
+ /// Asynchronously disposes this instance.
+ ///
+ /// A task representing the asynchronous dispose operation.
+ public async ValueTask DisposeAsync()
+ {
+ await this.DisposeAsyncCore().ConfigureAwait(false);
+ this.Dispose(false);
+ GC.SuppressFinalize(this);
+ }
+
///
/// Executes JavaScript in the context of the currently selected frame or window.
///
@@ -593,6 +604,22 @@ protected virtual void Dispose(bool disposing)
}
}
+ ///
+ /// Asynchronously performs the core dispose logic.
+ ///
+ /// A task representing the asynchronous dispose operation.
+ protected virtual async ValueTask DisposeAsyncCore()
+ {
+ if (this.WrappedDriver is IAsyncDisposable asyncDisposable)
+ {
+ await asyncDisposable.DisposeAsync().ConfigureAwait(false);
+ }
+ else
+ {
+ this.WrappedDriver.Dispose();
+ }
+ }
+
///
/// Raises the event.
///
diff --git a/dotnet/src/webdriver/IWebDriver.cs b/dotnet/src/webdriver/IWebDriver.cs
index 7a92363acbaa3..9177ae81098a1 100644
--- a/dotnet/src/webdriver/IWebDriver.cs
+++ b/dotnet/src/webdriver/IWebDriver.cs
@@ -43,7 +43,7 @@ namespace OpenQA.Selenium;
/// more fully featured browser when there is a requirement for one.
///
///
-public interface IWebDriver : ISearchContext, IDisposable
+public interface IWebDriver : ISearchContext, IDisposable, IAsyncDisposable
{
///
/// Gets or sets the URL the browser is currently displaying.
diff --git a/dotnet/src/webdriver/WebDriver.cs b/dotnet/src/webdriver/WebDriver.cs
index 78c76f08ebadb..0df8343313859 100644
--- a/dotnet/src/webdriver/WebDriver.cs
+++ b/dotnet/src/webdriver/WebDriver.cs
@@ -223,6 +223,17 @@ public void Dispose()
GC.SuppressFinalize(this);
}
+ ///
+ /// Asynchronously disposes the WebDriver Instance
+ ///
+ /// A task representing the asynchronous dispose operation.
+ public async ValueTask DisposeAsync()
+ {
+ await this.DisposeAsyncCore().ConfigureAwait(false);
+ this.Dispose(false);
+ GC.SuppressFinalize(this);
+ }
+
///
/// Executes JavaScript "asynchronously" in the context of the currently selected frame or window,
/// executing the callback function specified as the last argument in the list of arguments.
@@ -672,25 +683,60 @@ protected bool RegisterInternalDriverCommand(string commandName, [NotNullWhen(tr
/// if its in the process of disposing
protected virtual void Dispose(bool disposing)
{
- try
+ if (disposing)
{
if (this.SessionId is not null)
{
- this.Execute(DriverCommand.Quit, null);
+ try
+ {
+
+ this.Execute(DriverCommand.Quit, null);
+
+ }
+ catch (NotImplementedException)
+ {
+ }
+ catch (InvalidOperationException)
+ {
+ }
+ catch (WebDriverException)
+ {
+ }
+ finally
+ {
+ this.SessionId = null!;
+ }
}
+
+ this.CommandExecutor.Dispose();
}
- catch (NotImplementedException)
- {
- }
- catch (InvalidOperationException)
- {
- }
- catch (WebDriverException)
- {
- }
- finally
+ }
+
+ ///
+ /// Asynchronously performs the core dispose logic.
+ ///
+ /// A task representing the asynchronous dispose operation.
+ protected virtual async ValueTask DisposeAsyncCore()
+ {
+ if (this.SessionId is not null)
{
- this.SessionId = null!;
+ try
+ {
+ await this.ExecuteAsync(DriverCommand.Quit, null).ConfigureAwait(false);
+ }
+ catch (NotImplementedException)
+ {
+ }
+ catch (InvalidOperationException)
+ {
+ }
+ catch (WebDriverException)
+ {
+ }
+ finally
+ {
+ this.SessionId = null!;
+ }
}
this.CommandExecutor.Dispose();
diff --git a/dotnet/test/common/StubDriver.cs b/dotnet/test/common/StubDriver.cs
index 9f14206c40196..64d6cfa821fa4 100644
--- a/dotnet/test/common/StubDriver.cs
+++ b/dotnet/test/common/StubDriver.cs
@@ -19,6 +19,7 @@
using System;
using System.Collections.ObjectModel;
+using System.Threading.Tasks;
namespace OpenQA.Selenium;
@@ -101,4 +102,13 @@ public void Dispose()
}
#endregion
+
+ #region IAsyncDisposable Members
+
+ public ValueTask DisposeAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
}
From 1e2dd9c68db7100c4f2b77a819c34b2f1129ced5 Mon Sep 17 00:00:00 2001
From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com>
Date: Thu, 26 Feb 2026 19:13:50 +0300
Subject: [PATCH 2/6] Dispose EventFiringWebDriver simpler
---
.../support/Events/EventFiringWebDriver.cs | 34 ++-----------------
1 file changed, 2 insertions(+), 32 deletions(-)
diff --git a/dotnet/src/support/Events/EventFiringWebDriver.cs b/dotnet/src/support/Events/EventFiringWebDriver.cs
index ee27380eec7e4..f189539cd8869 100644
--- a/dotnet/src/support/Events/EventFiringWebDriver.cs
+++ b/dotnet/src/support/Events/EventFiringWebDriver.cs
@@ -401,7 +401,7 @@ public ReadOnlyCollection FindElements(By by)
///
public void Dispose()
{
- this.Dispose(true);
+ WrappedDriver.Dispose();
GC.SuppressFinalize(this);
}
@@ -411,8 +411,7 @@ public void Dispose()
/// A task representing the asynchronous dispose operation.
public async ValueTask DisposeAsync()
{
- await this.DisposeAsyncCore().ConfigureAwait(false);
- this.Dispose(false);
+ await WrappedDriver.DisposeAsync().ConfigureAwait(false);
GC.SuppressFinalize(this);
}
@@ -591,35 +590,6 @@ public Screenshot GetScreenshot()
return screenshotDriver.GetScreenshot();
}
- ///
- /// Frees all managed and, optionally, unmanaged resources used by this instance.
- ///
- /// to dispose of only managed resources;
- /// to dispose of managed and unmanaged resources.
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- this.WrappedDriver.Dispose();
- }
- }
-
- ///
- /// Asynchronously performs the core dispose logic.
- ///
- /// A task representing the asynchronous dispose operation.
- protected virtual async ValueTask DisposeAsyncCore()
- {
- if (this.WrappedDriver is IAsyncDisposable asyncDisposable)
- {
- await asyncDisposable.DisposeAsync().ConfigureAwait(false);
- }
- else
- {
- this.WrappedDriver.Dispose();
- }
- }
-
///
/// Raises the event.
///
From 44e4832cd1dbc3b535b3a96ff2e5c775d61d90c6 Mon Sep 17 00:00:00 2001
From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com>
Date: Thu, 26 Feb 2026 19:57:34 +0300
Subject: [PATCH 3/6] Fix Chromium disposal pattern
---
.../src/webdriver/Chromium/ChromiumDriver.cs | 19 +++++++++++++++++--
dotnet/src/webdriver/Firefox/FirefoxDriver.cs | 11 -----------
2 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/dotnet/src/webdriver/Chromium/ChromiumDriver.cs b/dotnet/src/webdriver/Chromium/ChromiumDriver.cs
index 8857f5229b254..71f864f771513 100644
--- a/dotnet/src/webdriver/Chromium/ChromiumDriver.cs
+++ b/dotnet/src/webdriver/Chromium/ChromiumDriver.cs
@@ -480,9 +480,9 @@ public void StopCasting(string deviceName)
}
///
- /// Stops the driver from running
+ /// Disposes of the resources used by the instance, including any active DevTools session.
///
- /// if its in the process of disposing
+ /// Indicates whether the method is being called from a Dispose method (true) or from a finalizer (false).
protected override void Dispose(bool disposing)
{
if (disposing)
@@ -497,6 +497,21 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}
+ ///
+ /// Asynchronously disposes of the resources used by the instance, including any active DevTools session.
+ ///
+ /// A task representing the asynchronous dispose operation.
+ protected override async ValueTask DisposeAsyncCore()
+ {
+ if (this.devToolsSession != null)
+ {
+ this.devToolsSession.Dispose();
+ this.devToolsSession = null;
+ }
+
+ await base.DisposeAsyncCore().ConfigureAwait(false);
+ }
+
private static ICapabilities ConvertOptionsToCapabilities(ChromiumOptions options)
{
if (options == null)
diff --git a/dotnet/src/webdriver/Firefox/FirefoxDriver.cs b/dotnet/src/webdriver/Firefox/FirefoxDriver.cs
index 0573d2ed368e7..fd4d58c049eb5 100644
--- a/dotnet/src/webdriver/Firefox/FirefoxDriver.cs
+++ b/dotnet/src/webdriver/Firefox/FirefoxDriver.cs
@@ -414,17 +414,6 @@ protected virtual void PrepareEnvironment()
// Does nothing, but provides a hook for subclasses to do "stuff"
}
- ///
- /// Disposes of the FirefoxDriver and frees all resources.
- ///
- /// A value indicating whether the user initiated the
- /// disposal of the object. Pass if the user is actively
- /// disposing the object; otherwise .
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- }
-
private static ICapabilities ConvertOptionsToCapabilities(FirefoxOptions options)
{
if (options == null)
From 7fe86385d5f51170690d7d044af2fc756ed0b411 Mon Sep 17 00:00:00 2001
From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com>
Date: Thu, 26 Feb 2026 20:12:09 +0300
Subject: [PATCH 4/6] Fix build error
---
dotnet/test/support/Selenium.WebDriver.Support.Tests.csproj | 1 +
1 file changed, 1 insertion(+)
diff --git a/dotnet/test/support/Selenium.WebDriver.Support.Tests.csproj b/dotnet/test/support/Selenium.WebDriver.Support.Tests.csproj
index be6aaef57a009..f5f0ddde1f474 100644
--- a/dotnet/test/support/Selenium.WebDriver.Support.Tests.csproj
+++ b/dotnet/test/support/Selenium.WebDriver.Support.Tests.csproj
@@ -6,6 +6,7 @@
+
From 96275810b3823fcf2b196ff8fca55807978fcbc5 Mon Sep 17 00:00:00 2001
From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com>
Date: Thu, 26 Feb 2026 20:49:55 +0300
Subject: [PATCH 5/6] Implement good disposal pattern for EventFiringDriver
---
.../support/Events/EventFiringWebDriver.cs | 26 +++++++++++++++++--
dotnet/test/support/Events/BUILD.bazel | 1 +
.../Selenium.WebDriver.Support.Tests.csproj | 1 -
3 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/dotnet/src/support/Events/EventFiringWebDriver.cs b/dotnet/src/support/Events/EventFiringWebDriver.cs
index f189539cd8869..9933064e8871f 100644
--- a/dotnet/src/support/Events/EventFiringWebDriver.cs
+++ b/dotnet/src/support/Events/EventFiringWebDriver.cs
@@ -401,7 +401,7 @@ public ReadOnlyCollection FindElements(By by)
///
public void Dispose()
{
- WrappedDriver.Dispose();
+ this.Dispose(true);
GC.SuppressFinalize(this);
}
@@ -411,10 +411,32 @@ public void Dispose()
/// A task representing the asynchronous dispose operation.
public async ValueTask DisposeAsync()
{
- await WrappedDriver.DisposeAsync().ConfigureAwait(false);
+ await this.DisposeAsyncCore().ConfigureAwait(false);
+ this.Dispose(false);
GC.SuppressFinalize(this);
}
+ ///
+ /// Stops the client from running.
+ ///
+ /// If , managed resources are disposed.
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ WrappedDriver.Dispose();
+ }
+ }
+
+ ///
+ /// Asynchronously performs the core dispose logic.
+ ///
+ /// A task representing the asynchronous dispose operation.
+ protected virtual async ValueTask DisposeAsyncCore()
+ {
+ await WrappedDriver.DisposeAsync().ConfigureAwait(false);
+ }
+
///
/// Executes JavaScript in the context of the currently selected frame or window.
///
diff --git a/dotnet/test/support/Events/BUILD.bazel b/dotnet/test/support/Events/BUILD.bazel
index 060534a69a7bc..593314776f691 100644
--- a/dotnet/test/support/Events/BUILD.bazel
+++ b/dotnet/test/support/Events/BUILD.bazel
@@ -13,6 +13,7 @@ dotnet_nunit_test_suite(
"//dotnet/src/support",
"//dotnet/src/webdriver:webdriver-net8.0",
"//dotnet/test/common:fixtures",
+ nuget_package("Microsoft.Bcl.AsyncInterfaces"),
nuget_package("NUnit"),
nuget_package("Moq"),
],
diff --git a/dotnet/test/support/Selenium.WebDriver.Support.Tests.csproj b/dotnet/test/support/Selenium.WebDriver.Support.Tests.csproj
index f5f0ddde1f474..036e08d4bfd77 100644
--- a/dotnet/test/support/Selenium.WebDriver.Support.Tests.csproj
+++ b/dotnet/test/support/Selenium.WebDriver.Support.Tests.csproj
@@ -14,7 +14,6 @@
-
From b174cb3e8b2dd223918acca49a0a73a991bd364b Mon Sep 17 00:00:00 2001
From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com>
Date: Thu, 26 Feb 2026 21:04:36 +0300
Subject: [PATCH 6/6] Force good using in nuget README
---
dotnet/src/webdriver/assets/nuget/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dotnet/src/webdriver/assets/nuget/README.md b/dotnet/src/webdriver/assets/nuget/README.md
index e780243d3445b..e05cb38ab350b 100644
--- a/dotnet/src/webdriver/assets/nuget/README.md
+++ b/dotnet/src/webdriver/assets/nuget/README.md
@@ -6,7 +6,7 @@ Selenium is a set of different software tools each with a different approach to
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium;
-using var driver = new ChromeDriver();
+await using var driver = new ChromeDriver();
driver.Url = "https://www.google.com";
driver.FindElement(By.Name("q")).SendKeys("webdriver" + Keys.Return);