diff --git a/.skipped-tests b/.skipped-tests
index 91a20a0a5b629..61c07d731a010 100644
--- a/.skipped-tests
+++ b/.skipped-tests
@@ -1,5 +1,6 @@
-//dotnet/test/common:NetworkInterceptionTests-chrome
-//dotnet/test/common:NetworkInterceptionTests-edge
+-//dotnet/test/firefox:FirefoxDriverTest-firefox
-//java/test/org/openqa/selenium/chrome:ChromeDriverFunctionalTest
-//java/test/org/openqa/selenium/chrome:ChromeDriverFunctionalTest-remote
-//java/test/org/openqa/selenium/edge:EdgeDriverFunctionalTest
diff --git a/dotnet/src/webdriver/DriverService.cs b/dotnet/src/webdriver/DriverService.cs
index 86477f98c8c46..efa81509956cb 100644
--- a/dotnet/src/webdriver/DriverService.cs
+++ b/dotnet/src/webdriver/DriverService.cs
@@ -284,7 +284,7 @@ protected virtual void Dispose(bool disposing)
/// Raises the event.
///
/// A that contains the event data.
- protected void OnDriverProcessStarting(DriverProcessStartingEventArgs eventArgs)
+ protected virtual void OnDriverProcessStarting(DriverProcessStartingEventArgs eventArgs)
{
if (eventArgs == null)
{
@@ -298,7 +298,7 @@ protected void OnDriverProcessStarting(DriverProcessStartingEventArgs eventArgs)
/// Raises the event.
///
/// A that contains the event data.
- protected void OnDriverProcessStarted(DriverProcessStartedEventArgs eventArgs)
+ protected virtual void OnDriverProcessStarted(DriverProcessStartedEventArgs eventArgs)
{
if (eventArgs == null)
{
diff --git a/dotnet/src/webdriver/Firefox/FirefoxDriverService.cs b/dotnet/src/webdriver/Firefox/FirefoxDriverService.cs
index 223f5b58156a0..37c99b4927618 100644
--- a/dotnet/src/webdriver/Firefox/FirefoxDriverService.cs
+++ b/dotnet/src/webdriver/Firefox/FirefoxDriverService.cs
@@ -22,6 +22,7 @@
using System.Globalization;
using System.IO;
using System.Text;
+using System.Threading.Tasks;
namespace OpenQA.Selenium.Firefox;
@@ -32,6 +33,11 @@ public sealed class FirefoxDriverService : DriverService
{
private const string DefaultFirefoxDriverServiceFileName = "geckodriver";
+ ///
+ /// Process management fields for the log writer.
+ ///
+ private StreamWriter? logWriter;
+
///
/// Initializes a new instance of the class.
///
@@ -87,6 +93,16 @@ protected override DriverOptions GetDefaultDriverOptions()
///
public bool OpenBrowserToolbox { get; set; }
+ ///
+ /// Gets or sets the file path where log output should be written.
+ ///
+ ///
+ /// A or value indicates no log file to specify.
+ /// This approach takes the process output and redirects it to a file because GeckoDriver does not
+ /// offer a way to specify a log file path directly.
+ ///
+ public string? LogPath { get; set; }
+
///
/// Gets or sets the level at which log output is displayed.
///
@@ -177,6 +193,75 @@ protected override string CommandLineArguments
}
}
+ ///
+ /// Handles the event when the driver service process is starting.
+ ///
+ /// The event arguments containing information about the driver service process.
+ ///
+ /// This method initializes a log writer if a log path is specified and redirects output streams to capture logs.
+ ///
+ protected override void OnDriverProcessStarting(DriverProcessStartingEventArgs eventArgs)
+ {
+ if (!string.IsNullOrEmpty(this.LogPath))
+ {
+ string? directory = Path.GetDirectoryName(this.LogPath);
+ if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ // Initialize the log writer
+ logWriter = new StreamWriter(this.LogPath, append: true) { AutoFlush = true };
+
+ // Configure process to redirect output
+ eventArgs.DriverServiceProcessStartInfo.RedirectStandardOutput = true;
+ eventArgs.DriverServiceProcessStartInfo.RedirectStandardError = true;
+ }
+
+ base.OnDriverProcessStarting(eventArgs);
+ }
+
+ ///
+ /// Handles the event when the driver process has started.
+ ///
+ /// The event arguments containing information about the started driver process.
+ ///
+ /// This method reads the output and error streams asynchronously and writes them to the log file if available.
+ ///
+ protected override void OnDriverProcessStarted(DriverProcessStartedEventArgs eventArgs)
+ {
+ if (logWriter == null) return;
+ if (eventArgs.StandardOutputStreamReader != null)
+ {
+ _ = Task.Run(() => ReadStreamAsync(eventArgs.StandardOutputStreamReader));
+ }
+
+ if (eventArgs.StandardErrorStreamReader != null)
+ {
+ _ = Task.Run(() => ReadStreamAsync(eventArgs.StandardErrorStreamReader));
+ }
+
+ base.OnDriverProcessStarted(eventArgs);
+ }
+
+ ///
+ /// Disposes of the resources used by the instance.
+ ///
+ /// A value indicating whether the method is being called from Dispose.
+ ///
+ /// If disposing is true, it disposes of the log writer if it exists.
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (logWriter != null && disposing)
+ {
+ logWriter.Dispose();
+ logWriter = null;
+ }
+
+ base.Dispose(disposing);
+ }
+
///
/// Creates a default instance of the FirefoxDriverService.
///
@@ -258,4 +343,24 @@ private static string FirefoxDriverServiceFileName()
return fileName;
}
+
+ private async Task ReadStreamAsync(StreamReader reader)
+ {
+ try
+ {
+ string? line;
+ while ((line = await reader.ReadLineAsync()) != null)
+ {
+ if (logWriter != null)
+ {
+ logWriter.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} {line}");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ // Log or handle the exception appropriately
+ System.Diagnostics.Debug.WriteLine($"Error reading stream: {ex.Message}");
+ }
+ }
}
diff --git a/dotnet/test/firefox/BUILD.bazel b/dotnet/test/firefox/BUILD.bazel
new file mode 100644
index 0000000000000..398b528554122
--- /dev/null
+++ b/dotnet/test/firefox/BUILD.bazel
@@ -0,0 +1,27 @@
+load("//dotnet:defs.bzl", "dotnet_nunit_test_suite", "framework")
+
+dotnet_nunit_test_suite(
+ name = "LargeTests",
+ size = "large",
+ srcs = glob(
+ [
+ "**/*Test.cs",
+ "**/*Tests.cs",
+ ],
+ ) + [
+ "//dotnet/test/common:assembly-fixtures",
+ ],
+ browsers = [
+ "firefox",
+ ],
+ data = [
+ "//dotnet/test/common:test-data",
+ ],
+ target_frameworks = ["net8.0"],
+ deps = [
+ "//dotnet/src/support",
+ "//dotnet/src/webdriver:webdriver-net8.0",
+ "//dotnet/test/common:fixtures",
+ framework("nuget", "NUnit"),
+ ],
+)
diff --git a/dotnet/test/firefox/FirefoxDriverServiceTest.cs b/dotnet/test/firefox/FirefoxDriverServiceTest.cs
new file mode 100644
index 0000000000000..1b617316e65a3
--- /dev/null
+++ b/dotnet/test/firefox/FirefoxDriverServiceTest.cs
@@ -0,0 +1,53 @@
+//
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+using NUnit.Framework;
+using System.IO;
+
+namespace OpenQA.Selenium.Firefox;
+
+[TestFixture]
+public class FirefoxDriverServiceTest : DriverTestFixture
+{
+ [Test]
+ public void ShouldRedirectGeckoDriverLogsToFile()
+ {
+ FirefoxOptions options = new FirefoxOptions();
+ string logPath = Path.GetTempFileName();
+ options.LogLevel = FirefoxDriverLogLevel.Trace;
+
+ FirefoxDriverService service = FirefoxDriverService.CreateDefaultService();
+ service.LogPath = logPath;
+
+ IWebDriver driver2 = new FirefoxDriver(service, options);
+
+ try
+ {
+ Assert.That(File.Exists(logPath), Is.True);
+ string logContent = File.ReadAllText(logPath);
+ Assert.That(logContent, Does.Contain("geckodriver"));
+ }
+ finally
+ {
+ driver2.Quit();
+ File.Delete(logPath);
+ }
+ }
+
+}