diff --git a/dotnet/src/webdriver/DriverProcessStartedEventArgs.cs b/dotnet/src/webdriver/DriverProcessStartedEventArgs.cs index 5126576ab1767..0f48e2704302e 100644 --- a/dotnet/src/webdriver/DriverProcessStartedEventArgs.cs +++ b/dotnet/src/webdriver/DriverProcessStartedEventArgs.cs @@ -19,7 +19,6 @@ using System; using System.Diagnostics; -using System.IO; namespace OpenQA.Selenium; @@ -41,31 +40,10 @@ public DriverProcessStartedEventArgs(Process driverProcess) } this.ProcessId = driverProcess.Id; - if (driverProcess.StartInfo.RedirectStandardOutput && !driverProcess.StartInfo.UseShellExecute) - { - this.StandardOutputStreamReader = driverProcess.StandardOutput; - } - - if (driverProcess.StartInfo.RedirectStandardError && !driverProcess.StartInfo.UseShellExecute) - { - this.StandardErrorStreamReader = driverProcess.StandardError; - } } /// /// Gets the unique ID of the driver executable process. /// public int ProcessId { get; } - - /// - /// Gets a object that can be used to read the contents - /// printed to stdout by a driver service process. - /// - public StreamReader? StandardOutputStreamReader { get; } - - /// - /// Gets a object that can be used to read the contents - /// printed to stderr by a driver service process. - /// - public StreamReader? StandardErrorStreamReader { get; } } diff --git a/dotnet/src/webdriver/DriverService.cs b/dotnet/src/webdriver/DriverService.cs index efa81509956cb..cf4ecfd1d5288 100644 --- a/dotnet/src/webdriver/DriverService.cs +++ b/dotnet/src/webdriver/DriverService.cs @@ -26,6 +26,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; +using OpenQA.Selenium.Internal.Logging; namespace OpenQA.Selenium; @@ -37,6 +38,8 @@ public abstract class DriverService : ICommandServer private bool isDisposed; private Process? driverServiceProcess; + private static readonly ILogger _logger = Log.GetLogger(typeof(DriverService)); + /// /// Initializes a new instance of the class. /// @@ -243,6 +246,12 @@ public void Start() this.driverServiceProcess.StartInfo.UseShellExecute = false; this.driverServiceProcess.StartInfo.CreateNoWindow = this.HideCommandPromptWindow; + this.driverServiceProcess.StartInfo.RedirectStandardOutput = true; + this.driverServiceProcess.StartInfo.RedirectStandardError = true; + + this.driverServiceProcess.OutputDataReceived += this.OnDriverProcessDataReceived; + this.driverServiceProcess.ErrorDataReceived += this.OnDriverProcessDataReceived; + DriverProcessStartingEventArgs eventArgs = new DriverProcessStartingEventArgs(this.driverServiceProcess.StartInfo); this.OnDriverProcessStarting(eventArgs); @@ -251,6 +260,9 @@ public void Start() DriverProcessStartedEventArgs processStartedEventArgs = new DriverProcessStartedEventArgs(this.driverServiceProcess); this.OnDriverProcessStarted(processStartedEventArgs); + this.driverServiceProcess.BeginOutputReadLine(); + this.driverServiceProcess.BeginErrorReadLine(); + if (!serviceAvailable) { throw new WebDriverException($"Cannot start the driver service on {this.ServiceUrl}"); @@ -308,6 +320,23 @@ protected virtual void OnDriverProcessStarted(DriverProcessStartedEventArgs even this.DriverProcessStarted?.Invoke(this, eventArgs); } + /// + /// Handles the output and error data received from the driver process. + /// + /// The sender of the event. + /// The data received event arguments. + /// A value indicating whether the data received is from the error stream. + protected virtual void OnDriverProcessDataReceived(object sender, DataReceivedEventArgs args) + { + if (string.IsNullOrEmpty(args.Data)) + return; + + if (_logger.IsEnabled(LogEventLevel.Trace)) + { + _logger.Trace(args.Data); + } + } + /// /// Stops the DriverService. /// diff --git a/dotnet/src/webdriver/Firefox/FirefoxDriverService.cs b/dotnet/src/webdriver/Firefox/FirefoxDriverService.cs index cbaacd3d02148..6825a48a32562 100644 --- a/dotnet/src/webdriver/Firefox/FirefoxDriverService.cs +++ b/dotnet/src/webdriver/Firefox/FirefoxDriverService.cs @@ -19,10 +19,10 @@ using OpenQA.Selenium.Internal; using System; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Text; -using System.Threading.Tasks; namespace OpenQA.Selenium.Firefox; @@ -223,11 +223,11 @@ protected override string CommandLineArguments } /// - /// Handles the event when the driver service process is starting. + /// Called when the driver process is starting. This method sets up log file writing if a log path is specified. /// /// 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. + /// This method initializes a log writer if a log path is specified. /// protected override void OnDriverProcessStarting(DriverProcessStartingEventArgs eventArgs) { @@ -239,40 +239,37 @@ protected override void OnDriverProcessStarting(DriverProcessStartingEventArgs e 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. + /// Handles the output and error data received from the driver process and sends it to the log writer if available. /// - /// 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) + /// The sender of the event. + /// The data received event arguments. + /// A value indicating whether the data received is from the error stream. + protected override void OnDriverProcessDataReceived(object sender, DataReceivedEventArgs args) { - if (logWriter == null) return; - if (eventArgs.StandardOutputStreamReader != null) + if (string.IsNullOrEmpty(args.Data)) + return; + + if (!string.IsNullOrEmpty(this.LogPath)) { - _ = Task.Run(() => ReadStreamAsync(eventArgs.StandardOutputStreamReader)); + if (logWriter != null) + { + logWriter.WriteLine(args.Data); + } } - - if (eventArgs.StandardErrorStreamReader != null) + else { - _ = Task.Run(() => ReadStreamAsync(eventArgs.StandardErrorStreamReader)); + base.OnDriverProcessDataReceived(sender, args); } - - base.OnDriverProcessStarted(eventArgs); } + /// /// Disposes of the resources used by the instance. /// @@ -372,24 +369,4 @@ 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/FirefoxDriverServiceTest.cs b/dotnet/test/firefox/FirefoxDriverServiceTest.cs index 1b617316e65a3..24ae78d2d0138 100644 --- a/dotnet/test/firefox/FirefoxDriverServiceTest.cs +++ b/dotnet/test/firefox/FirefoxDriverServiceTest.cs @@ -17,25 +17,52 @@ // under the License. // +using System; +using System.Collections.Generic; using NUnit.Framework; using System.IO; +using System.Linq; +using OpenQA.Selenium.Internal.Logging; namespace OpenQA.Selenium.Firefox; [TestFixture] -public class FirefoxDriverServiceTest : DriverTestFixture +public class FirefoxDriverServiceTest { + private TestLogHandler testLogHandler; + + private void ResetGlobalLog() + { + Log.SetLevel(LogEventLevel.Info); + Log.Handlers.Clear().Handlers.Add(new TextWriterHandler(Console.Error)); + } + + [SetUp] + public void SetUp() + { + ResetGlobalLog(); + + testLogHandler = new TestLogHandler(); + } + + [TearDown] + public void TearDown() + { + ResetGlobalLog(); + } + [Test] public void ShouldRedirectGeckoDriverLogsToFile() { FirefoxOptions options = new FirefoxOptions(); string logPath = Path.GetTempFileName(); - options.LogLevel = FirefoxDriverLogLevel.Trace; + options.LogLevel = FirefoxDriverLogLevel.Info; FirefoxDriverService service = FirefoxDriverService.CreateDefaultService(); service.LogPath = logPath; - IWebDriver driver2 = new FirefoxDriver(service, options); + IWebDriver firefoxDriver = new FirefoxDriver(service, options); + firefoxDriver.Quit(); try { @@ -45,9 +72,44 @@ public void ShouldRedirectGeckoDriverLogsToFile() } finally { - driver2.Quit(); File.Delete(logPath); } } + [Test] + public void ShouldRedirectGeckoDriverLogsToConsole() + { + Log.SetLevel(LogEventLevel.Trace).Handlers.Add(testLogHandler); + FirefoxOptions options = new FirefoxOptions(); + options.LogLevel = FirefoxDriverLogLevel.Info; + + FirefoxDriverService service = FirefoxDriverService.CreateDefaultService(); + + IWebDriver firefoxDriver = new FirefoxDriver(service, options); + + try + { + Assert.That(testLogHandler.Events, Has.Count.AtLeast(1)); + Assert.That(testLogHandler.Events.Any(e => e.Message.Contains("geckodriver")), Is.True); + } + finally + { + firefoxDriver.Quit(); + } + } +} + +class TestLogHandler : ILogHandler +{ + public ILogHandler Clone() + { + return this; + } + + public void Handle(LogEvent logEvent) + { + Events.Add(logEvent); + } + + public IList Events { get; internal set; } = new List(); }