Skip to content

Conversation

@nvborisenko
Copy link
Member

The changes modernize how browser and driver paths are discovered, enhance logging capabilities, and simplify the interface for driver discovery.

🔗 Related Issues

Contributes to #13989

💥 What does this PR do?

  • Replaced the old BinaryPaths method and argument-string construction with a new DiscoverBrowser API that takes structured options, builds arguments internally, and returns a DiscoveryResult object. This makes the API clearer, more robust, and easier to extend.
  • Removed the manual argument-building logic from DriverFinder and now use the new SeleniumManager.DiscoverBrowser method for driver and browser path resolution.
  • Updated references throughout DriverFinder to use local constants for key names instead of referencing them from SeleniumManager.
  • Enhanced the process execution logic for Selenium Manager: now supports timeouts, improved error and output logging, and more robust error messages in case of process failure or timeout.
  • Extended the logging interfaces (ILogger, ILogContext) to support emitting log messages with explicit timestamps, and updated implementations accordingly.

🔄 Types of changes

  • Cleanup (formatting, renaming)
  • New feature (non-breaking change which adds functionality and tests!)
  • Breaking change (fix or feature that would cause existing functionality to change)

@selenium-ci selenium-ci added the C-dotnet .NET Bindings label Jan 29, 2026
@qodo-code-review
Copy link
Contributor

PR Type

Enhancement, Tests


Description

  • Modernized Selenium Manager API with structured DiscoverBrowser method replacing string-based BinaryPaths

  • Added support for explicit log timestamps and mixed output parsing from Selenium Manager process

  • Implemented process timeout handling and improved error message formatting

  • Extended logging interfaces to support timestamped log messages with specific severity levels

  • Removed manual argument construction logic, now handled internally by discovery options


File Walkthrough

Relevant files
Enhancement
DriverFinder.cs
Refactor to use new structured discovery API                         

dotnet/src/webdriver/DriverFinder.cs

  • Replaced BinaryPaths() method call with new
    SeleniumManager.DiscoverBrowser() API using structured
    DiscoveryOptions
  • Removed CreateArguments() method that manually built command-line
    arguments
  • Added local constants DriverPathKey and BrowserPathKey instead of
    referencing from SeleniumManager
  • Updated to use DiscoveryResult object properties instead of dictionary
    lookups
+22/-47 
ILogContext.cs
Add timestamp parameter to log emission                                   

dotnet/src/webdriver/Internal/Logging/ILogContext.cs

  • Added timestamp parameter to EmitMessage method signature
  • Updated method to accept explicit DateTimeOffset for log event timing
+2/-1     
ILogger.cs
Add timestamped log message method                                             

dotnet/src/webdriver/Internal/Logging/ILogger.cs

  • Added new LogMessage method accepting timestamp, log level, and
    message
  • Enables logging with explicit timestamps from external sources
+8/-0     
Logger.cs
Implement timestamped logging support                                       

dotnet/src/webdriver/Internal/Logging/Logger.cs

  • Implemented new LogMessage method that accepts explicit timestamp
  • Updated private LogMessage method to use DateTimeOffset.UtcNow when
    calling public method
  • Refactored to delegate timestamp handling to the new public method
+6/-1     
SeleniumManager.cs
Introduce structured discovery API with mixed output parsing

dotnet/src/webdriver/SeleniumManager.cs

  • Replaced BinaryPaths(string arguments) with new DiscoverBrowser(string
    browserName, DiscoveryOptions? options) method
  • Introduced DiscoveryOptions record with properties for browser
    version, path, driver version, proxy, and timeout
  • Introduced DiscoveryResult record replacing the nested response types
  • Changed output format from json to mixed to capture both structured
    data and log messages
  • Implemented parsing of Selenium Manager log output from stderr with
    regex pattern matching for timestamp and log level extraction
  • Added timeout support with process kill on timeout and improved error
    messages
  • Added debug/trace logging for process execution and binary location
  • Removed old SeleniumManagerResponse and nested response types
  • Updated JSON serialization context to use new DiscoveryResult type
  • Implemented local log message handlers to parse and emit timestamped
    logs
+188/-81

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 29, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Command argument injection

Description: User-controlled values from browserName and DiscoveryOptions are interpolated into a
single command-line string (e.g., --browser "{0}", --browser-path "{0}", --proxy "{0}"),
which can enable argument-injection if inputs contain quotes or crafted sequences that
break quoting and add unintended flags.
SeleniumManager.cs [197-226]

Referred Code
public static DiscoveryResult DiscoverBrowser(string browserName, DiscoveryOptions? options = null)
{
    if (string.IsNullOrEmpty(browserName))
    {
        throw new ArgumentException("Browser name must be specified to find the driver using Selenium Manager.", nameof(browserName));
    }

    StringBuilder argsBuilder = new();

    argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --browser \"{0}\"", browserName);

    if (!string.IsNullOrEmpty(options?.BrowserVersion))
    {
        argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --browser-version \"{0}\"", options?.BrowserVersion);
    }

    if (!string.IsNullOrEmpty(options?.BrowserPath))
    {
        argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --browser-path \"{0}\"", options?.BrowserPath);
    }



 ... (clipped 9 lines)
Ticket Compliance
🟡
🎫 #13989
🟢 Switch Selenium Manager invocation from `--output json` to `--output mixed`.
Stream Selenium Manager output into the binding's internal logging instead of
printing/logging all output at the end.
🔴 Use --log-level rather than --debug for controlling Selenium Manager logging verbosity.
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Log level mismatch: Selenium Manager stderr parsing expects WARNING but the regex captures WARN, causing
warnings to be misclassified and undermining reliable error/log handling.

Referred Code
switch (logLevel)
{
    case "INFO":
        _logger.LogMessage(dateTime, LogEventLevel.Info, message);
        break;
    case "WARNING":
        _logger.LogMessage(dateTime, LogEventLevel.Warn, message);
        break;
    case "ERROR":
        _logger.LogMessage(dateTime, LogEventLevel.Error, message);
        break;
    case "DEBUG":
        _logger.LogMessage(dateTime, LogEventLevel.Debug, message);
        break;
    case "TRACE":
    default:
        _logger.LogMessage(dateTime, LogEventLevel.Trace, message);
        break;

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Sensitive error details: Exceptions embed full process command/arguments and raw Selenium Manager output, which can
expose local paths and proxy details to consumers.

Referred Code
        var exceptionMessageBuilder = new StringBuilder($"Selenium Manager process exited abnormally with {process.ExitCode} code: {process.StartInfo.FileName} {arguments}");

        if (!string.IsNullOrWhiteSpace(errOutputBuilder.ToString()))
        {
            exceptionMessageBuilder.AppendLine();
            exceptionMessageBuilder.AppendLine("--- Error Output ---");
            exceptionMessageBuilder.Append(errOutputBuilder);
            exceptionMessageBuilder.AppendLine("--- End Error Output ---");
        }

        throw new WebDriverException(exceptionMessageBuilder.ToString());
    }
}
catch (Exception ex)
{
    throw new WebDriverException($"Error starting process: {process.StartInfo.FileName} {arguments}", ex);
}
finally
{
    process.OutputDataReceived -= HandleStandardOutput;
    process.ErrorDataReceived -= HandleErrorOutput;


 ... (clipped 14 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Logs may leak secrets: The info log prints the full Selenium Manager command line including --proxy (which may
contain credentials) and other sensitive paths/values.

Referred Code
if (_logger.IsEnabled(LogEventLevel.Info))
{
    _logger.Info($"Starting Selenium Manager process: {Path.GetFileName(smBinaryPath)} {arguments}");
}

Learn more about managing compliance generic rules or creating your own custom rules

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

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 29, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix greedy regex for log parsing

Make the timestamp-matching part of the log parsing regex non-greedy by changing
(.) to (.?) to ensure correct parsing if the timestamp contains spaces.

dotnet/src/webdriver/SeleniumManager.cs [416]

-const string LogMessageRegexPattern = @"^\[(.*) (INFO|WARN|ERROR|DEBUG|TRACE)\t?\] (.*)$";
+const string LogMessageRegexPattern = @"^\[(.*?) (INFO|WARN|ERROR|DEBUG|TRACE)\t?\] (.*)$";
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential issue with greedy matching in the log parsing regex and proposes a fix to make it non-greedy, which improves the robustness of the log parsing logic.

Medium
Improve process timeout exception handling

Improve the process timeout exception message to be more accurate when no
timeout is specified, avoiding confusing messages like "timed out after -1 ms".

dotnet/src/webdriver/SeleniumManager.cs [276-281]

-if (!process.WaitForExit(timeout is null ? -1 : (int)timeout.Value.TotalMilliseconds))
+int timeoutMilliseconds = timeout.HasValue ? (int)timeout.Value.TotalMilliseconds : -1;
+if (!process.WaitForExit(timeoutMilliseconds))
 {
     process.Kill();
-
-    throw new WebDriverException($"Selenium Manager process timed out after {(timeout ?? TimeSpan.FromMilliseconds(-1)).TotalMilliseconds} ms");
+    if (timeout.HasValue)
+    {
+        throw new WebDriverException($"Selenium Manager process timed out after {timeout.Value.TotalMilliseconds} ms");
+    }
+    else
+    {
+        throw new WebDriverException("Selenium Manager process failed to exit.");
+    }
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that the timeout exception message is misleading when no timeout is specified, and the proposed change provides a more accurate error message for that case.

Low
Learned
best practice
Prevent argument injection vulnerabilities

Build the process arguments using ProcessStartInfo.ArgumentList (or escape
inputs) so values like paths/proxy strings cannot break quoting or inject
additional flags.

dotnet/src/webdriver/SeleniumManager.cs [206-226]

-argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --browser \"{0}\"", browserName);
+var argumentList = new List<string>
+{
+    "--browser", browserName
+};
 
 if (!string.IsNullOrEmpty(options?.BrowserVersion))
 {
-    argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --browser-version \"{0}\"", options?.BrowserVersion);
+    argumentList.Add("--browser-version");
+    argumentList.Add(options.BrowserVersion);
 }
 
 if (!string.IsNullOrEmpty(options?.BrowserPath))
 {
-    argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --browser-path \"{0}\"", options?.BrowserPath);
+    argumentList.Add("--browser-path");
+    argumentList.Add(options.BrowserPath);
 }
 
 if (!string.IsNullOrEmpty(options?.DriverVersion))
 {
-    argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --driver-version \"{0}\"", options?.DriverVersion);
+    argumentList.Add("--driver-version");
+    argumentList.Add(options.DriverVersion);
 }
 
 if (!string.IsNullOrEmpty(options?.Proxy))
 {
-    argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --proxy \"{0}\"", options?.Proxy);
+    argumentList.Add("--proxy");
+    argumentList.Add(options.Proxy);
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Avoid shell/CLI argument injection by not building command lines via string interpolation; pass arguments as structured values (e.g., ProcessStartInfo.ArgumentList) or properly escape/sanitize user-derived inputs.

Low
Harden external log parsing
Suggestion Impact:The commit updated the log-level handling to use "WARN" in the switch (matching the regex token) and refactored regex usage, but it did not add DateTimeOffset.TryParse-based parsing.

code diff:

@@ -329,11 +336,7 @@
         {
             if (e.Data is not null)
             {
-#if NET8_0_OR_GREATER
-                var match = LogMessageRegex().Match(e.Data);
-#else
-                var match = Regex.Match(e.Data, LogMessageRegexPattern, RegexOptions.Compiled);
-#endif
+                var match = LogMessageRegex.Match(e.Data);
 
                 if (match.Success)
                 {
@@ -346,7 +349,7 @@
                         case "INFO":
                             _logger.LogMessage(dateTime, LogEventLevel.Info, message);
                             break;
-                        case "WARNING":
+                        case "WARN":
                             _logger.LogMessage(dateTime, LogEventLevel.Warn, message);
                             break;
                         case "ERROR":
@@ -417,7 +420,11 @@
 
 # if NET8_0_OR_GREATER
     [GeneratedRegex(LogMessageRegexPattern)]
-    private static partial Regex LogMessageRegex();
+    private static partial Regex GeneratedLogMessageRegex();
+
+    private static Regex LogMessageRegex { get; } = GeneratedLogMessageRegex();
+#else
+    private static Regex LogMessageRegex { get; } = new(LogMessageRegexPattern, RegexOptions.Compiled);
 #endif

Make the regex and switch consistent (e.g., WARN vs WARNING) and use
DateTimeOffset.TryParse (or TryParseExact) to avoid exceptions on unexpected log
lines.

dotnet/src/webdriver/SeleniumManager.cs [340-362]

 const string LogMessageRegexPattern = @"^\[(.*) (INFO|WARN|ERROR|DEBUG|TRACE)\t?\] (.*)$";
 ...
-var dateTime = DateTimeOffset.Parse(match.Groups[1].Value);
-var logLevel = match.Groups[2].Value;
-var message = match.Groups[3].Value;
+if (DateTimeOffset.TryParse(match.Groups[1].Value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dateTime))
+{
+    var logLevel = match.Groups[2].Value;
+    var message = match.Groups[3].Value;
 
-switch (logLevel)
+    switch (logLevel)
+    {
+        case "INFO":
+            _logger.LogMessage(dateTime, LogEventLevel.Info, message);
+            break;
+        case "WARN":
+            _logger.LogMessage(dateTime, LogEventLevel.Warn, message);
+            break;
+        case "ERROR":
+            _logger.LogMessage(dateTime, LogEventLevel.Error, message);
+            break;
+        case "DEBUG":
+            _logger.LogMessage(dateTime, LogEventLevel.Debug, message);
+            break;
+        case "TRACE":
+        default:
+            _logger.LogMessage(dateTime, LogEventLevel.Trace, message);
+            break;
+    }
+}
+else
 {
-    case "INFO":
-        _logger.LogMessage(dateTime, LogEventLevel.Info, message);
-        break;
-    case "WARNING":
-        _logger.LogMessage(dateTime, LogEventLevel.Warn, message);
-        break;
-    case "ERROR":
-        _logger.LogMessage(dateTime, LogEventLevel.Error, message);
-        break;
-    case "DEBUG":
-        _logger.LogMessage(dateTime, LogEventLevel.Debug, message);
-        break;
-    case "TRACE":
-    default:
-        _logger.LogMessage(dateTime, LogEventLevel.Trace, message);
-        break;
+    errOutputBuilder.AppendLine(e.Data);
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Validate and robustly parse external tool output; avoid brittle parsing assumptions and fail fast with clear errors when format is unexpected.

Low
High-level
Rethink the Selenium Manager API
Suggestion Impact:The commit removed the nested DiscoveryOptions and DiscoveryResult records from inside SeleniumManager and introduced new top-level record types (BrowserDiscoveryOptions and BrowserDiscoveryResult), updating DiscoverBrowser to use the new top-level types and serializer context accordingly.

code diff:

-    /// <summary>
-    /// Provides optional configuration for browser and driver discovery.
-    /// </summary>
-    public record DiscoveryOptions
-    {
-        /// <summary>
-        /// Gets or sets the specific browser version to target (e.g., "120.0.6099.109").
-        /// If not specified, the installed browser version is detected automatically.
-        /// </summary>
-        public string? BrowserVersion { get; set; }
-
-        /// <summary>
-        /// Gets or sets the path to the browser executable.
-        /// When specified, Selenium Manager uses this path instead of detecting the browser location.
-        /// </summary>
-        public string? BrowserPath { get; set; }
-
-        /// <summary>
-        /// Gets or sets the specific driver version to download (e.g., "120.0.6099.109").
-        /// If not specified, the driver version matching the browser version is selected automatically.
-        /// </summary>
-        public string? DriverVersion { get; set; }
-
-        /// <summary>
-        /// Gets or sets the proxy server URL for downloading browser drivers.
-        /// </summary>
-        public string? Proxy { get; set; }
-
-        /// <summary>
-        /// Gets or sets the timeout for the Selenium Manager process execution.
-        /// If not specified, the process will run without a timeout.
-        /// </summary>
-        public TimeSpan? Timeout { get; set; }
-    }
-
-    /// <summary>
-    /// Contains the paths to the discovered browser driver and browser executable.
-    /// </summary>
-    /// <param name="DriverPath">The absolute path to the browser driver executable.</param>
-    /// <param name="BrowserPath">The absolute path to the browser executable.</param>
-    public record DiscoveryResult(
-        [property: JsonPropertyName("driver_path")] string DriverPath,
-        [property: JsonPropertyName("browser_path")] string BrowserPath);
-
     const string LogMessageRegexPattern = @"^\[(.*) (INFO|WARN|ERROR|DEBUG|TRACE)\t?\] (.*)$";
 
-# if NET8_0_OR_GREATER
+#if NET8_0_OR_GREATER
     [GeneratedRegex(LogMessageRegexPattern)]
-    private static partial Regex LogMessageRegex();
+    private static partial Regex GeneratedLogMessageRegex();
+
+    private static Regex LogMessageRegex { get; } = GeneratedLogMessageRegex();
+#else
+    private static Regex LogMessageRegex { get; } = new(LogMessageRegexPattern, RegexOptions.Compiled);
 #endif
 }
 
-[JsonSerializable(typeof(SeleniumManager.DiscoveryResult))]
+/// <summary>
+/// Provides optional configuration for browser and driver discovery.
+/// </summary>
+public record BrowserDiscoveryOptions
+{
+    /// <summary>
+    /// Gets or sets the specific browser version to target (e.g., "120.0.6099.109").
+    /// If not specified, the installed browser version is detected automatically.
+    /// </summary>
+    public string? BrowserVersion { get; set; }
+
+    /// <summary>
+    /// Gets or sets the path to the browser executable.
+    /// When specified, Selenium Manager uses this path instead of detecting the browser location.
+    /// </summary>
+    public string? BrowserPath { get; set; }
+
+    /// <summary>
+    /// Gets or sets the specific driver version to download (e.g., "120.0.6099.109").
+    /// If not specified, the driver version matching the browser version is selected automatically.
+    /// </summary>
+    public string? DriverVersion { get; set; }
+
+    /// <summary>
+    /// Gets or sets the proxy server URL for downloading browser drivers.
+    /// </summary>
+    public string? Proxy { get; set; }
+
+    /// <summary>
+    /// Gets or sets the timeout for the Selenium Manager process execution.
+    /// If not specified, the process will run without a timeout.
+    /// </summary>
+    public TimeSpan? Timeout { get; set; }
+}
+
+/// <summary>
+/// Contains the paths to the discovered browser driver and browser executable.
+/// </summary>
+/// <param name="DriverPath">The absolute path to the browser driver executable.</param>
+/// <param name="BrowserPath">The absolute path to the browser executable.</param>
+public record BrowserDiscoveryResult(
+    [property: JsonPropertyName("driver_path")] string DriverPath,
+    [property: JsonPropertyName("browser_path")] string BrowserPath);
+
+[JsonSerializable(typeof(BrowserDiscoveryResult))]
 [JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)]
 internal sealed partial class SeleniumManagerSerializerContext : JsonSerializerContext;

Move the public DiscoveryOptions and DiscoveryResult records out of the
SeleniumManager class to become top-level types. This change improves API
discoverability and aligns with standard .NET design practices.

Examples:

dotnet/src/webdriver/SeleniumManager.cs [375-414]
    public record DiscoveryOptions
    {
        /// <summary>
        /// Gets or sets the specific browser version to target (e.g., "120.0.6099.109").
        /// If not specified, the installed browser version is detected automatically.
        /// </summary>
        public string? BrowserVersion { get; set; }

        /// <summary>
        /// Gets or sets the path to the browser executable.

 ... (clipped 30 lines)

Solution Walkthrough:

Before:

// file: dotnet/src/webdriver/SeleniumManager.cs
namespace OpenQA.Selenium;

public static partial class SeleniumManager
{
    public static DiscoveryResult DiscoverBrowser(...) { ... }

    public record DiscoveryOptions
    {
        // ... properties
    }

    public record DiscoveryResult(string DriverPath, string BrowserPath);
}

After:

// file: dotnet/src/webdriver/SeleniumManager.cs
namespace OpenQA.Selenium;

public static partial class SeleniumManager
{
    public static DiscoveryResult DiscoverBrowser(...) { ... }
}

public record DiscoveryOptions
{
    // ... properties
}

public record DiscoveryResult(string DriverPath, string BrowserPath);
Suggestion importance[1-10]: 5

__

Why: This is a valid API design suggestion that improves discoverability and aligns with .NET conventions, but it is not a critical functional issue.

Low
General
Check empty browser name

Use string.IsNullOrEmpty to validate options.BrowserName to handle both null and
empty strings.

dotnet/src/webdriver/DriverFinder.cs [113]

-if (options.BrowserName is null)
+if (string.IsNullOrEmpty(options.BrowserName))
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out that an empty string for BrowserName should also be handled, and using string.IsNullOrEmpty is a good practice for this validation.

Low
  • Update

Copilot AI review requested due to automatic review settings February 1, 2026 09:22
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 modernizes how Selenium Manager is invoked from the .NET bindings, introduces a structured DiscoverBrowser API, and wires Selenium Manager’s streaming output into the internal logging system with timestamped log entries.

Changes:

  • Replace the old BinaryPaths string-arguments API with a new DiscoverBrowser API returning a strongly typed DiscoveryResult, including options for browser/driver version, paths, proxy, and timeout.
  • Update DriverFinder to use SeleniumManager.DiscoverBrowser and local constants for driver/browser path keys instead of manually constructing arguments.
  • Extend the internal logging stack (ILogger, ILogContext, Logger) to support timestamped log emission, and use a regex-based parser to stream Selenium Manager logs into the .NET logging pipeline while enhancing process execution (timeouts, richer error messages).

Reviewed changes

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

Show a summary per file
File Description
dotnet/src/webdriver/SeleniumManager.cs Introduces the new DiscoverBrowser API, generic RunCommand with timeout and improved error logging, and regex-based streaming of Selenium Manager stderr logs into the internal logger, plus new DiscoveryOptions/DiscoveryResult types and source-generated JSON metadata.
dotnet/src/webdriver/DriverFinder.cs Switches from manual argument construction to calling SeleniumManager.DiscoverBrowser, stores results in a local paths cache keyed by internal constants, and validates the resolved driver/browser paths.
dotnet/src/webdriver/Internal/Logging/Logger.cs Adds a public LogMessage(DateTimeOffset, LogEventLevel, string) method to emit log events with explicit timestamps, and refactors existing log methods to use it.
dotnet/src/webdriver/Internal/Logging/LogContext.cs Updates EmitMessage to accept a timestamp and use it when creating LogEvent instances, aligning the context with the new timestamp-aware logging.
dotnet/src/webdriver/Internal/Logging/ILogger.cs Extends the ILogger interface with a timestamp-aware LogMessage method used by Selenium Manager’s log streaming.
dotnet/src/webdriver/Internal/Logging/ILogContext.cs Updates EmitMessage’s contract to include a timestamp parameter so logging contexts can preserve event time from external sources.

Copilot AI review requested due to automatic review settings February 1, 2026 20:49
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

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

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 1, 2026 20:57
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

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

Copilot AI review requested due to automatic review settings February 3, 2026 18:05
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

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

Comments suppressed due to low confidence (1)

dotnet/src/webdriver/SeleniumManager.cs:310

  • The generic catch block here wraps all exceptions, including WebDriverException instances thrown above (for non-zero exit codes or timeouts), which means the detailed error messages you build are only available in InnerException and the outer message is the more generic "Error starting process". This is a regression in error clarity and will make diagnosing Selenium Manager failures harder. Consider leaving existing WebDriverExceptions unwrapped (for example by using a catch (Exception ex) when (ex is not WebDriverException) filter) so callers see the rich process/timeout message directly, while still wrapping unexpected exceptions.
            if (process.ExitCode != 0)
            {
                var exceptionMessageBuilder = new StringBuilder($"Selenium Manager process exited abnormally with {process.ExitCode} code: {process.StartInfo.FileName} {arguments}");

                if (!string.IsNullOrWhiteSpace(stdOutputBuilder.ToString()))
                {
                    exceptionMessageBuilder.AppendLine();
                    exceptionMessageBuilder.AppendLine("--- Standard Output ---");
                    exceptionMessageBuilder.Append(stdOutputBuilder);
                    exceptionMessageBuilder.AppendLine("--- End Standard Output ---");
                }

                if (!string.IsNullOrWhiteSpace(errOutputBuilder.ToString()))
                {
                    exceptionMessageBuilder.AppendLine();
                    exceptionMessageBuilder.AppendLine("--- Error Output ---");
                    exceptionMessageBuilder.Append(errOutputBuilder);
                    exceptionMessageBuilder.AppendLine("--- End Error Output ---");
                }

                throw new WebDriverException(exceptionMessageBuilder.ToString());
            }
        }
        catch (Exception ex)
        {
            throw new WebDriverException($"Error starting process: {process.StartInfo.FileName} {arguments}", ex);

Comment on lines 333 to 375
void HandleStandardOutput(object sender, DataReceivedEventArgs e)
{
// Treat SM's logs always as Trace to avoid SM writing at Info level
if (_logger.IsEnabled(LogEventLevel.Trace))
stdOutputBuilder.AppendLine(e.Data);
}

void HandleErrorOutput(object sender, DataReceivedEventArgs e)
{
if (e.Data is not null)
{
foreach (var entry in jsonResponse.Logs)
var match = LogMessageRegex.Match(e.Data);

if (match.Success)
{
_logger.Trace($"{entry.Level} {entry.Message}");
var dateTime = DateTimeOffset.Parse(match.Groups[1].Value);
var logLevel = match.Groups[2].Value;
var message = match.Groups[3].Value;

switch (logLevel)
{
case "INFO":
_logger.LogMessage(dateTime, LogEventLevel.Info, message);
break;
case "WARN":
_logger.LogMessage(dateTime, LogEventLevel.Warn, message);
break;
case "ERROR":
_logger.LogMessage(dateTime, LogEventLevel.Error, message);
break;
case "DEBUG":
_logger.LogMessage(dateTime, LogEventLevel.Debug, message);
break;
case "TRACE":
default:
_logger.LogMessage(dateTime, LogEventLevel.Trace, message);
break;
}
}
else
{
errOutputBuilder.AppendLine(e.Data);
}
}
}
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

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

The new streaming of Selenium Manager stderr into the internal logging system (parsing log lines and mapping to ILogger.LogMessage) and the timeout behavior in RunCommand are not covered by tests, even though the logging subsystem already has dedicated tests in dotnet/test/common/Internal/Logging/LogTest.cs. Given the importance of this behavior for diagnosing driver discovery issues, consider adding focused tests (e.g., around the log-line parsing and error/timeout handling logic) to verify that different log levels are mapped correctly and that failures/timeouts surface the expected messages.

Copilot generated this review using guidance from repository custom instructions.
Copilot AI review requested due to automatic review settings February 3, 2026 19:11
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

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

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.

3 participants