Skip to content

[dotnet] Sending GeckoDriver output to a log file. #16081

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 25, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .skipped-tests
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions dotnet/src/webdriver/DriverService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ protected virtual void Dispose(bool disposing)
/// Raises the <see cref="DriverProcessStarting"/> event.
/// </summary>
/// <param name="eventArgs">A <see cref="DriverProcessStartingEventArgs"/> that contains the event data.</param>
protected void OnDriverProcessStarting(DriverProcessStartingEventArgs eventArgs)
protected virtual void OnDriverProcessStarting(DriverProcessStartingEventArgs eventArgs)
{
if (eventArgs == null)
{
Expand All @@ -298,7 +298,7 @@ protected void OnDriverProcessStarting(DriverProcessStartingEventArgs eventArgs)
/// Raises the <see cref="DriverProcessStarted"/> event.
/// </summary>
/// <param name="eventArgs">A <see cref="DriverProcessStartedEventArgs"/> that contains the event data.</param>
protected void OnDriverProcessStarted(DriverProcessStartedEventArgs eventArgs)
protected virtual void OnDriverProcessStarted(DriverProcessStartedEventArgs eventArgs)
{
if (eventArgs == null)
{
Expand Down
96 changes: 96 additions & 0 deletions dotnet/src/webdriver/Firefox/FirefoxDriverService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace OpenQA.Selenium.Firefox;

Expand All @@ -32,6 +33,11 @@ public sealed class FirefoxDriverService : DriverService
{
private const string DefaultFirefoxDriverServiceFileName = "geckodriver";

/// <summary>
/// Process management fields for the log writer.
/// </summary>
private StreamWriter? logWriter;

/// <summary>
/// Initializes a new instance of the <see cref="FirefoxDriverService"/> class.
/// </summary>
Expand Down Expand Up @@ -87,6 +93,16 @@ protected override DriverOptions GetDefaultDriverOptions()
/// </summary>
public bool OpenBrowserToolbox { get; set; }

/// <summary>
/// Gets or sets the file path where log output should be written.
/// </summary>
/// <remarks>
/// A <see langword="null"/> or <see cref="string.Empty"/> 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.
/// </remarks>
public string? LogPath { get; set; }

/// <summary>
/// Gets or sets the level at which log output is displayed.
/// </summary>
Expand Down Expand Up @@ -177,6 +193,74 @@ protected override string CommandLineArguments
}
}

/// <summary>
/// Handles the event when the driver service process is starting.
/// </summary>
/// <param name="eventArgs">The event arguments containing information about the driver service process.</param>
/// <remarks>
/// This method initializes a log writer if a log path is specified and redirects output streams to capture logs.
/// </remarks>
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);
}

/// <summary>
/// Handles the event when the driver process has started.
/// </summary>
/// <param name="eventArgs">The event arguments containing information about the started driver process.</param>
/// <remarks>
/// This method reads the output and error streams asynchronously and writes them to the log file if available.
/// </remarks>
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);
}

/// <summary>
/// Disposes of the resources used by the <see cref="FirefoxDriverService"/> instance.
/// </summary>
/// <param name="disposing">A value indicating whether the method is being called from Dispose.</param>
/// <remarks>
/// If disposing is true, it disposes of the log writer if it exists.
/// </remarks>
protected override void Dispose(bool disposing)
{
if (logWriter != null && disposing)
{
logWriter.Dispose();
}

base.Dispose(disposing);
}

/// <summary>
/// Creates a default instance of the FirefoxDriverService.
/// </summary>
Expand Down Expand Up @@ -258,4 +342,16 @@ private static string FirefoxDriverServiceFileName()

return fileName;
}

private async Task ReadStreamAsync(StreamReader reader)
{
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
if (logWriter != null)
{
logWriter.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} {line}");
}
}
}
}
27 changes: 27 additions & 0 deletions dotnet/test/firefox/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -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"),
],
)
53 changes: 53 additions & 0 deletions dotnet/test/firefox/FirefoxDriverServiceTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// <copyright file="FirefoxDriverServiceTest.cs" company="Selenium Committers">
// 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.
// </copyright>

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);
}
}

}