From 6b2463665b02ed58b7d36be51584d65937ef5b94 Mon Sep 17 00:00:00 2001
From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com>
Date: Sun, 3 Dec 2023 23:36:09 +0300
Subject: [PATCH] [dotnet] Internal logging (#13140)
* Update HttpCommandExecutor.cs
* Nullable handlers
* Don't capture logger
* Log message issuer
* Update nunit adapter to work with dotnet 7 and be more friendly with windows
* Introduce LogContextManager
* Make ILogger as static field
* Support hierarchical contexts
* Do not emit message to parent context
* Deep copy of loggers and handlers per context
* Static works with current log context
* Create context with minimum level
* Set minimum level for context
* Set minimum level per issuer
* Use DateTimeOffset
* Docs for everything
* Info minimum log level by default
* Adjust log levels in console output
* Make webdriver assembly internals visible to tests
* Make ILogger hidden from user
* Console handler by default
* Output logs to stderr
* New api to mange log handlers
* Verbose driver creation in tests
* Search driver type in loaded assemblies
* Declare internals visible to in csproj to not conflict with bazel
* Clean specific assembly name for driver type
* Fix targeting packs for test targets
---
dotnet/src/webdriver/BUILD.bazel | 5 +
dotnet/src/webdriver/Chrome/ChromeDriver.cs | 1 +
.../Internal/Logging/ConsoleLogHandler.cs | 49 ++++
.../webdriver/Internal/Logging/ILogContext.cs | 83 +++++++
.../webdriver/Internal/Logging/ILogHandler.cs | 38 ++++
.../Internal/Logging/ILogHandlerList.cs | 48 ++++
.../src/webdriver/Internal/Logging/ILogger.cs | 68 ++++++
dotnet/src/webdriver/Internal/Logging/Log.cs | 125 ++++++++++
.../webdriver/Internal/Logging/LogContext.cs | 143 ++++++++++++
.../Internal/Logging/LogContextManager.cs | 60 +++++
.../webdriver/Internal/Logging/LogEvent.cs | 63 +++++
.../Internal/Logging/LogEventLevel.cs | 56 +++++
.../Internal/Logging/LogHandlerList.cs | 57 +++++
.../src/webdriver/Internal/Logging/Logger.cs | 69 ++++++
.../webdriver/Remote/HttpCommandExecutor.cs | 13 ++
dotnet/src/webdriver/SeleniumManager.cs | 9 +-
dotnet/src/webdriver/WebDriver.csproj | 3 +
.../test/chrome/WebDriver.Chrome.Tests.csproj | 4 +-
dotnet/test/common/BUILD.bazel | 1 +
.../test/common/Environment/DriverConfig.cs | 8 -
.../test/common/Environment/DriverFactory.cs | 2 +
.../common/Environment/EnvironmentManager.cs | 18 +-
.../test/common/Environment/TestWebServer.cs | 15 +-
.../test/common/Internal/Logging/LogTest.cs | 215 ++++++++++++++++++
.../test/common/WebDriver.Common.Tests.csproj | 4 +-
dotnet/test/common/appconfig.json | 10 -
dotnet/test/edge/WebDriver.Edge.Tests.csproj | 4 +-
.../firefox/WebDriver.Firefox.Tests.csproj | 4 +-
dotnet/test/ie/WebDriver.IE.Tests.csproj | 4 +-
.../test/remote/WebDriver.Remote.Tests.csproj | 4 +-
.../test/safari/WebDriver.Safari.Tests.csproj | 4 +-
.../support/WebDriver.Support.Tests.csproj | 4 +-
32 files changed, 1146 insertions(+), 45 deletions(-)
create mode 100644 dotnet/src/webdriver/Internal/Logging/ConsoleLogHandler.cs
create mode 100644 dotnet/src/webdriver/Internal/Logging/ILogContext.cs
create mode 100644 dotnet/src/webdriver/Internal/Logging/ILogHandler.cs
create mode 100644 dotnet/src/webdriver/Internal/Logging/ILogHandlerList.cs
create mode 100644 dotnet/src/webdriver/Internal/Logging/ILogger.cs
create mode 100644 dotnet/src/webdriver/Internal/Logging/Log.cs
create mode 100644 dotnet/src/webdriver/Internal/Logging/LogContext.cs
create mode 100644 dotnet/src/webdriver/Internal/Logging/LogContextManager.cs
create mode 100644 dotnet/src/webdriver/Internal/Logging/LogEvent.cs
create mode 100644 dotnet/src/webdriver/Internal/Logging/LogEventLevel.cs
create mode 100644 dotnet/src/webdriver/Internal/Logging/LogHandlerList.cs
create mode 100644 dotnet/src/webdriver/Internal/Logging/Logger.cs
create mode 100644 dotnet/test/common/Internal/Logging/LogTest.cs
diff --git a/dotnet/src/webdriver/BUILD.bazel b/dotnet/src/webdriver/BUILD.bazel
index 317a07cea39d6..edf6087329f71 100644
--- a/dotnet/src/webdriver/BUILD.bazel
+++ b/dotnet/src/webdriver/BUILD.bazel
@@ -33,6 +33,10 @@ csharp_library(
"**/*.cs",
]) + devtools_version_targets(),
out = "WebDriver",
+ internals_visible_to = [
+ "WebDriver.Common.Tests",
+ ],
+ langversion = "10.0",
resources = [
"//javascript/atoms/fragments:find-elements.js",
"//javascript/atoms/fragments:is-displayed.js",
@@ -64,6 +68,7 @@ csharp_library(
]) + devtools_version_targets(),
out = "WebDriver.StrongNamed",
keyfile = "//dotnet:WebDriver.snk",
+ langversion = "10.0",
resources = [
"//javascript/atoms/fragments:find-elements.js",
"//javascript/atoms/fragments:is-displayed.js",
diff --git a/dotnet/src/webdriver/Chrome/ChromeDriver.cs b/dotnet/src/webdriver/Chrome/ChromeDriver.cs
index a22ae7dc75ac7..1fc39a4dce0e9 100644
--- a/dotnet/src/webdriver/Chrome/ChromeDriver.cs
+++ b/dotnet/src/webdriver/Chrome/ChromeDriver.cs
@@ -21,6 +21,7 @@
using System.Collections.ObjectModel;
using OpenQA.Selenium.Remote;
using OpenQA.Selenium.Chromium;
+using OpenQA.Selenium.Internal.Logging;
namespace OpenQA.Selenium.Chrome
{
diff --git a/dotnet/src/webdriver/Internal/Logging/ConsoleLogHandler.cs b/dotnet/src/webdriver/Internal/Logging/ConsoleLogHandler.cs
new file mode 100644
index 0000000000000..83759ac09dee6
--- /dev/null
+++ b/dotnet/src/webdriver/Internal/Logging/ConsoleLogHandler.cs
@@ -0,0 +1,49 @@
+//
+// 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 System;
+
+namespace OpenQA.Selenium.Internal.Logging
+{
+ ///
+ /// Represents a log handler that writes log events to the console.
+ ///
+ public class ConsoleLogHandler : ILogHandler
+ {
+ // performance trick to avoid expensive Enum.ToString() with fixed length
+ private static readonly string[] _levels = { "TRACE", "DEBUG", " INFO", " WARN", "ERROR" };
+
+ ///
+ /// Handles a log event by writing it to the console.
+ ///
+ /// The log event to handle.
+ public void Handle(LogEvent logEvent)
+ {
+ Console.Error.WriteLine($"{logEvent.Timestamp:HH:mm:ss.fff} {_levels[(int)logEvent.Level]} {logEvent.IssuedBy.Name}: {logEvent.Message}");
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// A new instance of the class.
+ public ILogHandler Clone()
+ {
+ return this;
+ }
+ }
+}
diff --git a/dotnet/src/webdriver/Internal/Logging/ILogContext.cs b/dotnet/src/webdriver/Internal/Logging/ILogContext.cs
new file mode 100644
index 0000000000000..e5c31328c982a
--- /dev/null
+++ b/dotnet/src/webdriver/Internal/Logging/ILogContext.cs
@@ -0,0 +1,83 @@
+//
+// 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 System;
+
+namespace OpenQA.Selenium.Internal.Logging
+{
+ ///
+ /// Represents a logging context that provides methods for creating sub-contexts, retrieving loggers, emitting log messages, and configuring minimum log levels.
+ ///
+ public interface ILogContext : IDisposable
+ {
+ ///
+ /// Creates a new logging context.
+ ///
+ /// A new instance of .
+ ILogContext CreateContext();
+
+ ///
+ /// Creates a new logging context with the specified minimum log level.
+ ///
+ /// The minimum log level for the new context.
+ /// A new instance of with the specified minimum log level.
+ ILogContext CreateContext(LogEventLevel minimumLevel);
+
+ ///
+ /// Gets a logger for the specified type.
+ ///
+ /// The type for which to retrieve the logger.
+ /// An instance of for the specified type.
+ internal ILogger GetLogger();
+
+ ///
+ /// Gets a logger for the specified type.
+ ///
+ /// The type for which to retrieve the logger.
+ /// An instance of for the specified type.
+ internal ILogger GetLogger(Type type);
+
+ ///
+ /// Emits a log message using the specified logger, log level, and message.
+ ///
+ /// The logger to emit the log message.
+ /// The log level of the message.
+ /// The log message.
+ internal void EmitMessage(ILogger logger, LogEventLevel level, string message);
+
+ ///
+ /// Sets the minimum log level for the current context.
+ ///
+ /// The minimum log level.
+ /// The current instance of with the minimum log level set.
+ ILogContext SetLevel(LogEventLevel level);
+
+ ///
+ /// Sets the minimum log level for the specified type in the current context.
+ ///
+ /// The type for which to set the minimum log level.
+ /// The minimum log level.
+ /// The current instance of with the minimum log level set for the specified type.
+ ILogContext SetLevel(Type issuer, LogEventLevel level);
+
+ ///
+ /// Gets a list of log handlers.
+ ///
+ ILogHandlerList Handlers { get; }
+ }
+}
diff --git a/dotnet/src/webdriver/Internal/Logging/ILogHandler.cs b/dotnet/src/webdriver/Internal/Logging/ILogHandler.cs
new file mode 100644
index 0000000000000..44c3ae67f6377
--- /dev/null
+++ b/dotnet/src/webdriver/Internal/Logging/ILogHandler.cs
@@ -0,0 +1,38 @@
+//
+// 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.
+//
+
+namespace OpenQA.Selenium.Internal.Logging
+{
+ ///
+ /// Represents a log handler that handles log events.
+ ///
+ public interface ILogHandler
+ {
+ ///
+ /// Handles a log event.
+ ///
+ /// The log event to handle.
+ void Handle(LogEvent logEvent);
+
+ ///
+ /// Creates a clone of the log handler.
+ ///
+ /// A clone of the log handler.
+ ILogHandler Clone();
+ }
+}
diff --git a/dotnet/src/webdriver/Internal/Logging/ILogHandlerList.cs b/dotnet/src/webdriver/Internal/Logging/ILogHandlerList.cs
new file mode 100644
index 0000000000000..60d63ca5251f9
--- /dev/null
+++ b/dotnet/src/webdriver/Internal/Logging/ILogHandlerList.cs
@@ -0,0 +1,48 @@
+//
+// 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 System.Collections.Generic;
+
+namespace OpenQA.Selenium.Internal.Logging
+{
+ ///
+ /// Represents a list of log handlers.
+ ///
+ public interface ILogHandlerList : IEnumerable
+ {
+ ///
+ /// Adds a log handler to the list.
+ ///
+ /// The log handler to add.
+ /// The log context.
+ ILogContext Add(ILogHandler handler);
+
+ ///
+ /// Removes a log handler from the list.
+ ///
+ /// The log handler to remove.
+ /// The log context.
+ ILogContext Remove(ILogHandler handler);
+
+ ///
+ /// Clears all log handlers from the list.
+ ///
+ /// The log context.
+ ILogContext Clear();
+ }
+}
diff --git a/dotnet/src/webdriver/Internal/Logging/ILogger.cs b/dotnet/src/webdriver/Internal/Logging/ILogger.cs
new file mode 100644
index 0000000000000..66e5af8f240c7
--- /dev/null
+++ b/dotnet/src/webdriver/Internal/Logging/ILogger.cs
@@ -0,0 +1,68 @@
+//
+// 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 System;
+
+namespace OpenQA.Selenium.Internal.Logging
+{
+ ///
+ /// Defines the interface through which log messages are emitted.
+ ///
+ internal interface ILogger
+ {
+ ///
+ /// Writes a trace-level log message.
+ ///
+ /// The log message.
+ void Trace(string message);
+
+ ///
+ /// Writes a debug-level log message.
+ ///
+ /// The log message.
+ void Debug(string message);
+
+ ///
+ /// Writes an info-level log message.
+ ///
+ /// The log message.
+ void Info(string message);
+
+ ///
+ /// Writes a warning-level log message.
+ ///
+ /// The log message.
+ void Warn(string message);
+
+ ///
+ /// Writes an error-level log message.
+ ///
+ /// The log message.
+ void Error(string message);
+
+ ///
+ /// Gets or sets the log event level.
+ ///
+ LogEventLevel Level { get; set; }
+
+ ///
+ /// Gets the type of the logger issuer.
+ ///
+ Type Issuer { get; }
+ }
+}
diff --git a/dotnet/src/webdriver/Internal/Logging/Log.cs b/dotnet/src/webdriver/Internal/Logging/Log.cs
new file mode 100644
index 0000000000000..a44054e13ba78
--- /dev/null
+++ b/dotnet/src/webdriver/Internal/Logging/Log.cs
@@ -0,0 +1,125 @@
+//
+// 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 System;
+
+namespace OpenQA.Selenium.Internal.Logging
+{
+ ///
+ /// Provides context aware logging functionality for the Selenium WebDriver.
+ ///
+ ///
+ ///
+ /// Use the following code to enable logging to console:
+ ///
+ /// Log.SetMinimumLevel(LogEventLevel.Debug)).WithHandler(new ConsoleLogHandler());
+ ///
+ ///
+ /// Or enable it per limited execution scope:
+ ///
+ /// using (var ctx = Log.CreateContext(LogEventLevel.Trace))
+ /// {
+ /// // do something
+ /// }
+ ///
+ ///
+ public static class Log
+ {
+ private static readonly LogContextManager _logContextManager = new LogContextManager();
+
+ ///
+ /// Creates a new log context with the current context properties and the specified minimum log event level.
+ ///
+ /// The created log context.
+ public static ILogContext CreateContext()
+ {
+ return _logContextManager.CurrentContext.CreateContext();
+ }
+
+ ///
+ /// Creates a new log context with with the current context properties and the specified minimum log event level.
+ ///
+ /// The minimum log event level.
+ /// The created log context.
+ public static ILogContext CreateContext(LogEventLevel minimumLevel)
+ {
+ return _logContextManager.CurrentContext.CreateContext(minimumLevel);
+ }
+
+ ///
+ /// Gets or sets the current log context.
+ ///
+ internal static ILogContext CurrentContext
+ {
+ get
+ {
+ return _logContextManager.CurrentContext;
+ }
+ set
+ {
+ _logContextManager.CurrentContext = value;
+ }
+ }
+
+ ///
+ /// Gets a logger for the specified type.
+ ///
+ /// The type to get the logger for.
+ /// The logger.
+ internal static ILogger GetLogger()
+ {
+ return _logContextManager.CurrentContext.GetLogger();
+ }
+
+ ///
+ /// Gets a logger for the specified type.
+ ///
+ /// The type to get the logger for.
+ /// The logger.
+ internal static ILogger GetLogger(Type type)
+ {
+ return _logContextManager.CurrentContext.GetLogger(type);
+ }
+
+ ///
+ /// Sets the minimum log event level for the current log context.
+ ///
+ /// The minimum log event level.
+ /// The current log context.
+ public static ILogContext SetLevel(LogEventLevel level)
+ {
+ return _logContextManager.CurrentContext.SetLevel(level);
+ }
+
+ ///
+ /// Sets the minimum log event level for the specified issuer in the current log context.
+ ///
+ /// The issuer type.
+ /// The minimum log event level.
+ /// The current log context.
+ public static ILogContext SetLevel(Type issuer, LogEventLevel level)
+ {
+ return _logContextManager.CurrentContext.SetLevel(issuer, level);
+ }
+
+ ///
+ /// Gets a list of log handlers for the current log context.
+ ///
+ public static ILogHandlerList Handlers => _logContextManager.CurrentContext.Handlers;
+ }
+}
diff --git a/dotnet/src/webdriver/Internal/Logging/LogContext.cs b/dotnet/src/webdriver/Internal/Logging/LogContext.cs
new file mode 100644
index 0000000000000..6b19059b5cd5a
--- /dev/null
+++ b/dotnet/src/webdriver/Internal/Logging/LogContext.cs
@@ -0,0 +1,143 @@
+//
+// 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 System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace OpenQA.Selenium.Internal.Logging
+{
+ ///
+ /// Represents a logging context that provides methods for creating sub-contexts, retrieving loggers, emitting log messages, and configuring minimum log levels.
+ ///
+ ///
+ internal class LogContext : ILogContext
+ {
+ private ConcurrentDictionary _loggers;
+
+ private LogEventLevel _level;
+
+ private readonly ILogContext _parentLogContext;
+
+ public LogContext(LogEventLevel level, ILogContext parentLogContext, ConcurrentDictionary loggers, ILogHandlerList handlers)
+ {
+ _level = level;
+
+ _parentLogContext = parentLogContext;
+
+ _loggers = loggers;
+
+ Handlers = handlers ?? new LogHandlerList(this);
+ }
+
+ public ILogContext CreateContext()
+ {
+ return CreateContext(_level);
+ }
+
+ public ILogContext CreateContext(LogEventLevel minimumLevel)
+ {
+ ConcurrentDictionary loggers = null;
+
+ if (_loggers != null)
+ {
+ loggers = new ConcurrentDictionary(_loggers.Select(l => new KeyValuePair(l.Key, new Logger(l.Value.Issuer, minimumLevel))));
+ }
+
+ IList handlers = null;
+
+ if (Handlers != null)
+ {
+ handlers = new List(Handlers.Select(h => h.Clone()));
+ }
+ else
+ {
+ handlers = new List();
+ }
+
+ var context = new LogContext(minimumLevel, this, loggers, Handlers);
+
+ Log.CurrentContext = context;
+
+ return context;
+ }
+
+ public ILogger GetLogger()
+ {
+ return GetLogger(typeof(T));
+ }
+
+ public ILogger GetLogger(Type type)
+ {
+ if (type == null)
+ {
+ throw new ArgumentNullException(nameof(type));
+ }
+
+ if (_loggers is null)
+ {
+ _loggers = new ConcurrentDictionary();
+ }
+
+ return _loggers.GetOrAdd(type, _ => new Logger(type, _level));
+ }
+
+ public void EmitMessage(ILogger logger, LogEventLevel level, string message)
+ {
+ if (Handlers != null && level >= _level && level >= GetLogger(logger.Issuer).Level)
+ {
+ var logEvent = new LogEvent(logger.Issuer, DateTimeOffset.Now, level, message);
+
+ foreach (var handler in Handlers)
+ {
+ handler.Handle(logEvent);
+ }
+ }
+ }
+
+ public ILogContext SetLevel(LogEventLevel level)
+ {
+ _level = level;
+
+ if (_loggers != null)
+ {
+ foreach (var logger in _loggers.Values)
+ {
+ logger.Level = _level;
+ }
+ }
+
+ return this;
+ }
+
+ public ILogContext SetLevel(Type issuer, LogEventLevel level)
+ {
+ GetLogger(issuer).Level = level;
+
+ return this;
+ }
+
+ public ILogHandlerList Handlers { get; }
+
+ public void Dispose()
+ {
+ Log.CurrentContext = _parentLogContext;
+ }
+ }
+}
diff --git a/dotnet/src/webdriver/Internal/Logging/LogContextManager.cs b/dotnet/src/webdriver/Internal/Logging/LogContextManager.cs
new file mode 100644
index 0000000000000..b1d64a0179d74
--- /dev/null
+++ b/dotnet/src/webdriver/Internal/Logging/LogContextManager.cs
@@ -0,0 +1,60 @@
+//
+// 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 System.Threading;
+
+namespace OpenQA.Selenium.Internal.Logging
+{
+ internal class LogContextManager
+ {
+ private readonly ILogContext _globalLogContext;
+
+ private readonly AsyncLocal _currentAmbientLogContext = new AsyncLocal();
+
+ public LogContextManager()
+ {
+ _globalLogContext = new LogContext(LogEventLevel.Info, null, null, null);
+
+ _globalLogContext.Handlers.Add(new ConsoleLogHandler());
+ }
+
+ public ILogContext GlobalContext
+ {
+ get { return _globalLogContext; }
+ }
+
+ public ILogContext CurrentContext
+ {
+ get
+ {
+ if (_currentAmbientLogContext.Value is null)
+ {
+ return _globalLogContext;
+ }
+ else
+ {
+ return _currentAmbientLogContext.Value;
+ }
+ }
+ set
+ {
+ _currentAmbientLogContext.Value = value;
+ }
+ }
+ }
+}
diff --git a/dotnet/src/webdriver/Internal/Logging/LogEvent.cs b/dotnet/src/webdriver/Internal/Logging/LogEvent.cs
new file mode 100644
index 0000000000000..103194070ee81
--- /dev/null
+++ b/dotnet/src/webdriver/Internal/Logging/LogEvent.cs
@@ -0,0 +1,63 @@
+//
+// 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 System;
+
+namespace OpenQA.Selenium.Internal.Logging
+{
+ ///
+ /// Represents a log event in the Selenium WebDriver internal logging system.
+ ///
+ public sealed class LogEvent
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type that issued the log event.
+ /// The timestamp of the log event.
+ /// The level of the log event.
+ /// The message of the log event.
+ public LogEvent(Type issuedBy, DateTimeOffset timestamp, LogEventLevel level, string message)
+ {
+ IssuedBy = issuedBy;
+ Timestamp = timestamp;
+ Level = level;
+ Message = message;
+ }
+
+ ///
+ /// Gets the type that issued the log event.
+ ///
+ public Type IssuedBy { get; }
+
+ ///
+ /// Gets the timestamp of the log event.
+ ///
+ public DateTimeOffset Timestamp { get; }
+
+ ///
+ /// Gets the level of the log event.
+ ///
+ public LogEventLevel Level { get; }
+
+ ///
+ /// Gets the message of the log event.
+ ///
+ public string Message { get; }
+ }
+}
diff --git a/dotnet/src/webdriver/Internal/Logging/LogEventLevel.cs b/dotnet/src/webdriver/Internal/Logging/LogEventLevel.cs
new file mode 100644
index 0000000000000..61e60f910de92
--- /dev/null
+++ b/dotnet/src/webdriver/Internal/Logging/LogEventLevel.cs
@@ -0,0 +1,56 @@
+//
+// 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.
+//
+
+namespace OpenQA.Selenium.Internal.Logging
+{
+ ///
+ /// Defines the levels of logging events.
+ ///
+ public enum LogEventLevel
+ {
+ ///
+ /// The most detailed log events.
+ ///
+ Trace = 0,
+
+ ///
+ /// Log events that are useful for debugging purposes.
+ ///
+ Debug = 1,
+
+ ///
+ /// Log events that provide general information about the application's operation.
+ ///
+ Info = 2,
+
+ ///
+ /// Log events that indicate a potential problem or a non-critical error.
+ ///
+ Warn = 3,
+
+ ///
+ /// Log events that indicate a serious error or a failure that requires immediate attention.
+ ///
+ Error = 4,
+
+ ///
+ /// No log events.
+ ///
+ None = 5
+ }
+}
diff --git a/dotnet/src/webdriver/Internal/Logging/LogHandlerList.cs b/dotnet/src/webdriver/Internal/Logging/LogHandlerList.cs
new file mode 100644
index 0000000000000..ad826cc539861
--- /dev/null
+++ b/dotnet/src/webdriver/Internal/Logging/LogHandlerList.cs
@@ -0,0 +1,57 @@
+//
+// 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 System.Collections.Generic;
+
+namespace OpenQA.Selenium.Internal.Logging
+{
+ ///
+ /// Represents a list of log handlers.
+ ///
+ ///
+ internal class LogHandlerList : List, ILogHandlerList
+ {
+ private readonly ILogContext _logContext;
+
+ public LogHandlerList(ILogContext logContext)
+ {
+ _logContext = logContext;
+ }
+
+ public new ILogContext Add(ILogHandler handler)
+ {
+ base.Add(handler);
+
+ return _logContext;
+ }
+
+ public new ILogContext Remove(ILogHandler handler)
+ {
+ base.Remove(handler);
+
+ return _logContext;
+ }
+
+ public new ILogContext Clear()
+ {
+ base.Clear();
+
+ return _logContext;
+ }
+ }
+}
diff --git a/dotnet/src/webdriver/Internal/Logging/Logger.cs b/dotnet/src/webdriver/Internal/Logging/Logger.cs
new file mode 100644
index 0000000000000..9bb2cece9ee6c
--- /dev/null
+++ b/dotnet/src/webdriver/Internal/Logging/Logger.cs
@@ -0,0 +1,69 @@
+//
+// 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 System;
+
+namespace OpenQA.Selenium.Internal.Logging
+{
+ ///
+ /// The implementation of the interface through which log messages are emitted.
+ ///
+ ///
+ internal class Logger : ILogger
+ {
+ public Logger(Type issuer, LogEventLevel level)
+ {
+ Issuer = issuer;
+ Level = level;
+ }
+
+ public LogEventLevel Level { get; set; }
+
+ public Type Issuer { get; internal set; }
+
+ public void Trace(string message)
+ {
+ LogMessage(LogEventLevel.Trace, message);
+ }
+
+ public void Debug(string message)
+ {
+ LogMessage(LogEventLevel.Debug, message);
+ }
+
+ public void Info(string message)
+ {
+ LogMessage(LogEventLevel.Info, message);
+ }
+
+ public void Warn(string message)
+ {
+ LogMessage(LogEventLevel.Warn, message);
+ }
+
+ public void Error(string message)
+ {
+ LogMessage(LogEventLevel.Error, message);
+ }
+
+ private void LogMessage(LogEventLevel level, string message)
+ {
+ Log.CurrentContext.EmitMessage(this, level, message);
+ }
+ }
+}
diff --git a/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs b/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs
index 0b69c9dccf05b..5599241182b9c 100644
--- a/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs
+++ b/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs
@@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net;
@@ -27,6 +28,7 @@
using System.Threading;
using System.Threading.Tasks;
using OpenQA.Selenium.Internal;
+using OpenQA.Selenium.Internal.Logging;
namespace OpenQA.Selenium.Remote
{
@@ -50,6 +52,8 @@ public class HttpCommandExecutor : ICommandExecutor
private CommandInfoRepository commandInfoRepository = new W3CWireProtocolCommandInfoRepository();
private HttpClient client;
+ private static readonly ILogger _logger = Log.GetLogger();
+
///
/// Initializes a new instance of the class
///
@@ -162,6 +166,8 @@ public virtual Response Execute(Command commandToExecute)
throw new ArgumentNullException(nameof(commandToExecute), "commandToExecute cannot be null");
}
+ _logger.Debug($"Executing command: {commandToExecute}");
+
HttpCommandInfo info = this.commandInfoRepository.GetCommandInfo(commandToExecute.Name);
if (info == null)
{
@@ -191,6 +197,9 @@ public virtual Response Execute(Command commandToExecute)
}
Response toReturn = this.CreateResponse(responseInfo);
+
+ _logger.Debug($"Response: {toReturn}");
+
return toReturn;
}
@@ -270,8 +279,12 @@ private async Task MakeHttpRequest(HttpRequestInfo requestInfo
requestMessage.Content.Headers.ContentType = contentTypeHeader;
}
+ _logger.Trace($">> {requestMessage}");
+
using (HttpResponseMessage responseMessage = await this.client.SendAsync(requestMessage).ConfigureAwait(false))
{
+ _logger.Trace($"<< {responseMessage}");
+
HttpResponseInfo httpResponseInfo = new HttpResponseInfo();
httpResponseInfo.Body = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
httpResponseInfo.ContentType = responseMessage.Content.Headers.ContentType?.ToString();
diff --git a/dotnet/src/webdriver/SeleniumManager.cs b/dotnet/src/webdriver/SeleniumManager.cs
index 96785b3d06f06..eb25d522a906e 100644
--- a/dotnet/src/webdriver/SeleniumManager.cs
+++ b/dotnet/src/webdriver/SeleniumManager.cs
@@ -18,6 +18,7 @@
using Newtonsoft.Json;
using OpenQA.Selenium.Internal;
+using OpenQA.Selenium.Internal.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -34,6 +35,8 @@ namespace OpenQA.Selenium
///
public static class SeleniumManager
{
+ private static readonly ILogger _logger = Log.GetLogger(typeof(SeleniumManager));
+
private static readonly string BinaryFullPath = Environment.GetEnvironmentVariable("SE_MANAGER_PATH");
static SeleniumManager()
@@ -115,7 +118,11 @@ public static string DriverPath(DriverOptions options)
// Cannot set Browser Location for this driver and that is ok
}
- return (string)output["driver_path"];
+ var driverPath = (string)output["driver_path"];
+
+ _logger.Trace($"Driver path: {driverPath}");
+
+ return driverPath;
}
///
diff --git a/dotnet/src/webdriver/WebDriver.csproj b/dotnet/src/webdriver/WebDriver.csproj
index 85abd8152ee1d..24df12ee0da74 100644
--- a/dotnet/src/webdriver/WebDriver.csproj
+++ b/dotnet/src/webdriver/WebDriver.csproj
@@ -4,6 +4,7 @@
netstandard2.0
WebDriver
OpenQA.Selenium
+ 10.0
@@ -42,6 +43,8 @@
+
+
diff --git a/dotnet/test/chrome/WebDriver.Chrome.Tests.csproj b/dotnet/test/chrome/WebDriver.Chrome.Tests.csproj
index d63c0e61a6e86..0d168ea50c237 100644
--- a/dotnet/test/chrome/WebDriver.Chrome.Tests.csproj
+++ b/dotnet/test/chrome/WebDriver.Chrome.Tests.csproj
@@ -24,8 +24,8 @@
-
-
+
+
diff --git a/dotnet/test/common/BUILD.bazel b/dotnet/test/common/BUILD.bazel
index 6f0742a0c6540..98b925cd30a6c 100644
--- a/dotnet/test/common/BUILD.bazel
+++ b/dotnet/test/common/BUILD.bazel
@@ -63,6 +63,7 @@ dotnet_nunit_test_suite(
]) + [
":assembly-fixtures",
],
+ out = "WebDriver.Common.Tests",
browsers = [
# The first browser in this list is assumed to be the one that should
# be used by default.
diff --git a/dotnet/test/common/Environment/DriverConfig.cs b/dotnet/test/common/Environment/DriverConfig.cs
index 17932b9c950d5..4f81e97086cab 100644
--- a/dotnet/test/common/Environment/DriverConfig.cs
+++ b/dotnet/test/common/Environment/DriverConfig.cs
@@ -1,10 +1,5 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace OpenQA.Selenium.Environment
{
@@ -14,9 +9,6 @@ public class DriverConfig
[JsonProperty]
public string DriverTypeName { get; set; }
- [JsonProperty]
- public string AssemblyName { get; set; }
-
[JsonProperty]
[JsonConverter(typeof(StringEnumConverter))]
public Browser BrowserValue { get; set; }
diff --git a/dotnet/test/common/Environment/DriverFactory.cs b/dotnet/test/common/Environment/DriverFactory.cs
index e0b5d705a2d77..11cf7d00f9390 100644
--- a/dotnet/test/common/Environment/DriverFactory.cs
+++ b/dotnet/test/common/Environment/DriverFactory.cs
@@ -55,6 +55,8 @@ public IWebDriver CreateDriver(Type driverType, bool logging = false)
public IWebDriver CreateDriverWithOptions(Type driverType, DriverOptions driverOptions, bool logging = false)
{
+ Console.WriteLine($"Creating new driver of {driverType} type...");
+
Browser browser = Browser.All;
DriverService service = null;
DriverOptions options = null;
diff --git a/dotnet/test/common/Environment/EnvironmentManager.cs b/dotnet/test/common/Environment/EnvironmentManager.cs
index 7aa60cb577601..ac606787d5fa2 100644
--- a/dotnet/test/common/Environment/EnvironmentManager.cs
+++ b/dotnet/test/common/Environment/EnvironmentManager.cs
@@ -5,6 +5,7 @@
using Newtonsoft.Json;
using NUnit.Framework;
using OpenQA.Selenium.Internal;
+using System.Linq;
namespace OpenQA.Selenium.Environment
{
@@ -58,17 +59,18 @@ private EnvironmentManager()
this.driverFactory = new DriverFactory(driverServiceLocation, browserLocation);
this.driverFactory.DriverStarting += OnDriverStarting;
- Assembly driverAssembly = null;
- try
- {
- driverAssembly = Assembly.Load(driverConfig.AssemblyName);
- }
- catch (FileNotFoundException)
+ // Search for the driver type in the all assemblies,
+ // bazel uses unpredictable assembly names to execute tests
+ driverType = AppDomain.CurrentDomain.GetAssemblies()
+ .Reverse()
+ .Select(assembly => assembly.GetType(driverConfig.DriverTypeName))
+ .FirstOrDefault(t => t != null);
+
+ if (driverType == null)
{
- driverAssembly = Assembly.GetExecutingAssembly();
+ throw new ArgumentOutOfRangeException($"Unable to find driver type {driverConfig.DriverTypeName}");
}
- driverType = driverAssembly.GetType(driverConfig.DriverTypeName);
browser = driverConfig.BrowserValue;
remoteCapabilities = driverConfig.RemoteCapabilities;
diff --git a/dotnet/test/common/Environment/TestWebServer.cs b/dotnet/test/common/Environment/TestWebServer.cs
index 5cb8645a68353..b5381a1c0b3a2 100644
--- a/dotnet/test/common/Environment/TestWebServer.cs
+++ b/dotnet/test/common/Environment/TestWebServer.cs
@@ -4,6 +4,7 @@
using System.Net;
using System.Diagnostics;
using System.Text;
+using System.Runtime.InteropServices;
using System.Net.Http;
namespace OpenQA.Selenium.Environment
@@ -45,6 +46,11 @@ public void Start()
standaloneTestJar = Path.Combine(baseDirectory, "../../../../../../bazel-bin/java/test/org/openqa/selenium/environment/appserver");
}
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ standaloneTestJar += ".exe";
+ }
+
Console.Write("Standalone jar is " + standaloneTestJar);
if (!File.Exists(standaloneTestJar))
@@ -148,8 +154,13 @@ public void Start()
public void Stop()
{
- using var httpClient = new HttpClient();
- using var quitResponse = httpClient.GetAsync(EnvironmentManager.Instance.UrlBuilder.LocalWhereIs("quitquitquit")).GetAwaiter().GetResult();
+ using (var httpClient = new HttpClient())
+ {
+ using (var quitResponse = httpClient.GetAsync(EnvironmentManager.Instance.UrlBuilder.LocalWhereIs("quitquitquit")).GetAwaiter().GetResult())
+ {
+
+ }
+ }
if (webserverProcess != null)
{
diff --git a/dotnet/test/common/Internal/Logging/LogTest.cs b/dotnet/test/common/Internal/Logging/LogTest.cs
new file mode 100644
index 0000000000000..037e94faefec6
--- /dev/null
+++ b/dotnet/test/common/Internal/Logging/LogTest.cs
@@ -0,0 +1,215 @@
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+
+namespace OpenQA.Selenium.Internal.Logging
+{
+ public class LogTest
+ {
+ private TestLogHandler testLogHandler;
+ private ILogger logger;
+
+ [SetUp]
+ public void SetUp()
+ {
+ testLogHandler = new TestLogHandler();
+ logger = Log.GetLogger();
+ }
+
+ [Test]
+ public void LoggerShouldEmitEvent()
+ {
+ Log.SetLevel(LogEventLevel.Info).Handlers.Add(testLogHandler);
+
+ logger.Info("test message");
+
+ Assert.That(testLogHandler.Events, Has.Count.EqualTo(1));
+
+ var logEvent = testLogHandler.Events[0];
+ Assert.That(logEvent.Level, Is.EqualTo(LogEventLevel.Info));
+ Assert.That(logEvent.Message, Is.EqualTo("test message"));
+ Assert.That(logEvent.IssuedBy, Is.EqualTo(typeof(LogTest)));
+ Assert.That(logEvent.Timestamp, Is.EqualTo(DateTimeOffset.Now).Within(100).Milliseconds);
+ }
+
+ [Test]
+ [TestCase(LogEventLevel.Trace)]
+ [TestCase(LogEventLevel.Debug)]
+ [TestCase(LogEventLevel.Info)]
+ [TestCase(LogEventLevel.Warn)]
+ [TestCase(LogEventLevel.Error)]
+ public void LoggerShouldEmitEventWithProperLevel(LogEventLevel level)
+ {
+ Log.SetLevel(level).Handlers.Add(testLogHandler);
+
+ switch (level)
+ {
+ case LogEventLevel.Trace:
+ logger.Trace("test message");
+ break;
+ case LogEventLevel.Debug:
+ logger.Debug("test message");
+ break;
+ case LogEventLevel.Info:
+ logger.Info("test message");
+ break;
+ case LogEventLevel.Warn:
+ logger.Warn("test message");
+ break;
+ case LogEventLevel.Error:
+ logger.Error("test message");
+ break;
+ }
+
+ Assert.That(testLogHandler.Events, Has.Count.EqualTo(1));
+
+ Assert.That(testLogHandler.Events[0].Level, Is.EqualTo(level));
+ }
+
+ [Test]
+ public void LoggerShouldNotEmitEventWhenLevelIsLess()
+ {
+ Log.SetLevel(LogEventLevel.Info).Handlers.Add(testLogHandler);
+
+ logger.Trace("test message");
+
+ Assert.That(testLogHandler.Events, Has.Count.EqualTo(0));
+ }
+
+ [Test]
+ public void ShouldGetProperLogger()
+ {
+ Log.SetLevel(LogEventLevel.Info);
+
+ var logger = Log.GetLogger();
+
+ Assert.That(logger.Issuer, Is.EqualTo(typeof(LogTest)));
+ Assert.That(logger.Level, Is.EqualTo(LogEventLevel.Info));
+ }
+
+ [Test]
+ public void ShouldCacheLogger()
+ {
+ var logger1 = Log.GetLogger();
+ var logger2 = Log.GetLogger();
+
+ Assert.That(logger1, Is.SameAs(logger2));
+ }
+
+ [Test]
+ public void ShouldCreateContext()
+ {
+ Log.SetLevel(LogEventLevel.Info);
+
+ using var context = Log.CreateContext();
+
+ var logger = context.GetLogger();
+
+ Assert.That(logger.Level, Is.EqualTo(LogEventLevel.Info));
+ }
+
+ [Test]
+ public void ShouldCreateContextWithCustomLevel()
+ {
+ Log.SetLevel(LogEventLevel.Info);
+
+ using var context = Log.CreateContext(LogEventLevel.Warn);
+
+ var logger = context.GetLogger();
+
+ Assert.That(logger.Level, Is.EqualTo(LogEventLevel.Warn));
+ }
+
+ [Test]
+ public void ContextShouldChangeLevel()
+ {
+ Log.SetLevel(LogEventLevel.Info);
+
+ using var context = Log.CreateContext();
+
+ context.SetLevel(LogEventLevel.Warn);
+
+ var logger = context.GetLogger();
+
+ Assert.That(logger.Level, Is.EqualTo(LogEventLevel.Warn));
+ }
+
+ [Test]
+ public void ShouldCreateContextWithCustomHandler()
+ {
+ Log.SetLevel(LogEventLevel.Info);
+
+ using var context = Log.CreateContext().Handlers.Add(testLogHandler);
+
+ var logger = context.GetLogger();
+
+ logger.Info("test message");
+
+ Assert.That(testLogHandler.Events, Has.Count.EqualTo(1));
+ }
+
+ [Test]
+ public void ShouldCreateSubContext()
+ {
+ Log.SetLevel(LogEventLevel.Info);
+
+ using var context = Log.CreateContext();
+
+ Assert.That(Log.CurrentContext, Is.SameAs(context));
+
+ using var subContext = context.CreateContext();
+
+ Assert.That(Log.CurrentContext, Is.SameAs(subContext));
+
+ var logger = subContext.GetLogger();
+
+ Assert.That(logger.Level, Is.EqualTo(LogEventLevel.Info));
+ }
+
+ [Test]
+ public void ShouldCreateSubContextFromCurrentContext()
+ {
+ Log.SetLevel(LogEventLevel.Info);
+
+ using var context = Log.CreateContext();
+
+ Assert.That(Log.CurrentContext, Is.SameAs(context));
+
+ using var subContext = Log.CreateContext();
+
+ Assert.That(Log.CurrentContext, Is.SameAs(subContext));
+
+ var logger = subContext.GetLogger();
+
+ Assert.That(logger.Level, Is.EqualTo(LogEventLevel.Info));
+ }
+
+ [Test]
+ public void ShouldCapturePreviousContextWhenCurrentFinishes()
+ {
+ using var globalContext = Log.CurrentContext;
+
+ using (var subContext = Log.CreateContext())
+ {
+ Assert.That(Log.CurrentContext, Is.SameAs(subContext));
+ }
+
+ Assert.That(Log.CurrentContext, Is.SameAs(globalContext));
+ }
+ }
+
+ class TestLogHandler : ILogHandler
+ {
+ public ILogHandler Clone()
+ {
+ return this;
+ }
+
+ public void Handle(LogEvent logEvent)
+ {
+ Events.Add(logEvent);
+ }
+
+ public IList Events { get; internal set; } = new List();
+ }
+}
diff --git a/dotnet/test/common/WebDriver.Common.Tests.csproj b/dotnet/test/common/WebDriver.Common.Tests.csproj
index ed9b250727217..c0f4075b1d439 100644
--- a/dotnet/test/common/WebDriver.Common.Tests.csproj
+++ b/dotnet/test/common/WebDriver.Common.Tests.csproj
@@ -25,9 +25,9 @@
-
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/dotnet/test/common/appconfig.json b/dotnet/test/common/appconfig.json
index 9ed48075d594c..0871ec6ed9a9f 100644
--- a/dotnet/test/common/appconfig.json
+++ b/dotnet/test/common/appconfig.json
@@ -26,7 +26,6 @@
"DriverConfigs": {
"Chrome": {
"DriverTypeName": "OpenQA.Selenium.Chrome.StableChannelChromeDriver",
- "AssemblyName": "WebDriver.Common.Tests",
"BrowserValue": "Chrome",
"RemoteCapabilities": "chrome",
"Logging": false
@@ -34,7 +33,6 @@
"ChromeDev": {
"DriverTypeName": "OpenQA.Selenium.Chrome.DevChannelChromeDriver",
- "AssemblyName": "WebDriver.Common.Tests",
"BrowserValue": "Chrome",
"RemoteCapabilities": "chrome",
"Logging": false
@@ -42,7 +40,6 @@
"EdgeIEMode": {
"DriverTypeName": "OpenQA.Selenium.IE.EdgeInternetExplorerModeDriver",
- "AssemblyName": "WebDriver.Common.Tests",
"BrowserValue": "IE",
"RemoteCapabilities": "internet explorer",
"Logging": false
@@ -50,7 +47,6 @@
"Edge": {
"DriverTypeName": "OpenQA.Selenium.Edge.StableChannelEdgeDriver",
- "AssemblyName": "WebDriver.Common.Tests",
"BrowserValue": "Edge",
"RemoteCapabilities": "MicrosoftEdge",
"Logging": false
@@ -58,7 +54,6 @@
"EdgeDev": {
"DriverTypeName": "OpenQA.Selenium.Edge.DevChannelEdgeDriver",
- "AssemblyName": "WebDriver.Common.Tests",
"BrowserValue": "Edge",
"RemoteCapabilities": "MicrosoftEdge",
"Logging": false
@@ -66,7 +61,6 @@
"Firefox": {
"DriverTypeName": "OpenQA.Selenium.Firefox.StableChannelFirefoxDriver",
- "AssemblyName": "WebDriver.Common.Tests",
"BrowserValue": "Firefox",
"RemoteCapabilities": "firefox",
"Logging": false
@@ -74,7 +68,6 @@
"FirefoxNightly": {
"DriverTypeName": "OpenQA.Selenium.Firefox.NightlyChannelFirefoxDriver",
- "AssemblyName": "WebDriver.Common.Tests",
"BrowserValue": "Firefox",
"RemoteCapabilities": "firefox",
"Logging": false
@@ -82,7 +75,6 @@
"Safari": {
"DriverTypeName": "OpenQA.Selenium.Safari.DefaultSafariDriver",
- "AssemblyName": "WebDriver.Common.Tests",
"BrowserValue": "Safari",
"RemoteCapabilities": "safari",
"Logging": false
@@ -90,7 +82,6 @@
"SafariTechPreview": {
"DriverTypeName": "OpenQA.Selenium.Safari.SafariTechnologyPreviewDriver",
- "AssemblyName": "WebDriver.Common.Tests",
"BrowserValue": "Safari",
"RemoteCapabilities": "safari",
"Logging": false
@@ -98,7 +89,6 @@
"Remote": {
"DriverTypeName": "OpenQA.Selenium.Remote.StableChannelRemoteChromeDriver",
- "AssemblyName": "WebDriver.Common.Tests",
"BrowserValue": "Remote",
"RemoteCapabilities": "chrome",
"AutoStartRemoteServer": true,
diff --git a/dotnet/test/edge/WebDriver.Edge.Tests.csproj b/dotnet/test/edge/WebDriver.Edge.Tests.csproj
index 23c4e4f4fadd1..411dd22b73e55 100644
--- a/dotnet/test/edge/WebDriver.Edge.Tests.csproj
+++ b/dotnet/test/edge/WebDriver.Edge.Tests.csproj
@@ -26,8 +26,8 @@
-
-
+
+
diff --git a/dotnet/test/firefox/WebDriver.Firefox.Tests.csproj b/dotnet/test/firefox/WebDriver.Firefox.Tests.csproj
index e3dda77d41329..e75039f3f4bb6 100644
--- a/dotnet/test/firefox/WebDriver.Firefox.Tests.csproj
+++ b/dotnet/test/firefox/WebDriver.Firefox.Tests.csproj
@@ -37,8 +37,8 @@
-
-
+
+
diff --git a/dotnet/test/ie/WebDriver.IE.Tests.csproj b/dotnet/test/ie/WebDriver.IE.Tests.csproj
index 85eec3747b968..262410e1b705e 100644
--- a/dotnet/test/ie/WebDriver.IE.Tests.csproj
+++ b/dotnet/test/ie/WebDriver.IE.Tests.csproj
@@ -25,8 +25,8 @@
-
-
+
+
diff --git a/dotnet/test/remote/WebDriver.Remote.Tests.csproj b/dotnet/test/remote/WebDriver.Remote.Tests.csproj
index c925d28af20ca..56edf029a66a3 100644
--- a/dotnet/test/remote/WebDriver.Remote.Tests.csproj
+++ b/dotnet/test/remote/WebDriver.Remote.Tests.csproj
@@ -22,8 +22,8 @@
-
-
+
+
diff --git a/dotnet/test/safari/WebDriver.Safari.Tests.csproj b/dotnet/test/safari/WebDriver.Safari.Tests.csproj
index eb3e5e4205a59..1700c54bcf225 100644
--- a/dotnet/test/safari/WebDriver.Safari.Tests.csproj
+++ b/dotnet/test/safari/WebDriver.Safari.Tests.csproj
@@ -23,8 +23,8 @@
-
-
+
+
diff --git a/dotnet/test/support/WebDriver.Support.Tests.csproj b/dotnet/test/support/WebDriver.Support.Tests.csproj
index 103fb1359569b..7fb4107b4b938 100644
--- a/dotnet/test/support/WebDriver.Support.Tests.csproj
+++ b/dotnet/test/support/WebDriver.Support.Tests.csproj
@@ -23,8 +23,8 @@
-
-
+
+