diff --git a/MODULE.bazel b/MODULE.bazel
index 348c66a2bf10d..bd8225d29fc95 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -17,7 +17,7 @@ bazel_dep(name = "protobuf", version = "21.7", dev_dependency = True, repo_name
# Required for rules_rust to import the crates properly
bazel_dep(name = "rules_cc", version = "0.0.9", dev_dependency = True)
-bazel_dep(name = "rules_dotnet", version = "0.15.1")
+bazel_dep(name = "rules_dotnet", version = "0.16.0")
bazel_dep(name = "rules_java", version = "7.11.1")
bazel_dep(name = "rules_jvm_external", version = "6.3")
bazel_dep(name = "rules_nodejs", version = "6.2.0")
@@ -178,8 +178,7 @@ maven.install(
"com.google.auto.service:auto-service:1.1.1",
"com.google.auto.service:auto-service-annotations:1.1.1",
"com.google.googlejavaformat:google-java-format:jar:1.23.0",
- "com.graphql-java:graphql-java:20.2",
- "com.graphql-java:java-dataloader:3.2.0",
+ "com.graphql-java:graphql-java:22.3",
"dev.failsafe:failsafe:3.3.2",
"io.grpc:grpc-context:1.66.0",
"io.lettuce:lettuce-core:6.4.0.RELEASE",
diff --git a/README.md b/README.md
index e5ef14de8c4f4..637c6428c3198 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
# Selenium
[![CI](https://github.com/SeleniumHQ/selenium/actions/workflows/ci.yml/badge.svg?branch=trunk&event=schedule)](https://github.com/SeleniumHQ/selenium/actions/workflows/ci.yml)
+[![Releases downloads](https://img.shields.io/github/downloads/SeleniumHQ/selenium/total.svg)](https://github.com/SeleniumHQ/selenium/releases)
diff --git a/dotnet/src/support/UI/LoadableComponentException.cs b/dotnet/src/support/UI/LoadableComponentException.cs
index 64354a240ac12..305543f50a3cd 100644
--- a/dotnet/src/support/UI/LoadableComponentException.cs
+++ b/dotnet/src/support/UI/LoadableComponentException.cs
@@ -57,17 +57,5 @@ public LoadableComponentException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected LoadableComponentException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/support/UI/UnexpectedTagNameException.cs b/dotnet/src/support/UI/UnexpectedTagNameException.cs
index 9cea8a3781e2b..9fc85a8f39bb7 100644
--- a/dotnet/src/support/UI/UnexpectedTagNameException.cs
+++ b/dotnet/src/support/UI/UnexpectedTagNameException.cs
@@ -69,17 +69,5 @@ public UnexpectedTagNameException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected UnexpectedTagNameException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/BUILD.bazel b/dotnet/src/webdriver/BUILD.bazel
index 1f23755fa1025..afcdd0785bae4 100644
--- a/dotnet/src/webdriver/BUILD.bazel
+++ b/dotnet/src/webdriver/BUILD.bazel
@@ -55,6 +55,7 @@ csharp_library(
framework("nuget", "Microsoft.Bcl.AsyncInterfaces"),
framework("nuget", "System.Threading.Tasks.Extensions"),
framework("nuget", "System.Memory"),
+ framework("nuget", "System.Text.Encodings.Web"),
framework("nuget", "System.Text.Json"),
],
)
@@ -119,6 +120,7 @@ csharp_library(
framework("nuget", "Microsoft.Bcl.AsyncInterfaces"),
framework("nuget", "System.Threading.Tasks.Extensions"),
framework("nuget", "System.Memory"),
+ framework("nuget", "System.Text.Encodings.Web"),
framework("nuget", "System.Text.Json"),
],
)
diff --git a/dotnet/src/webdriver/DetachedShadowRootException.cs b/dotnet/src/webdriver/DetachedShadowRootException.cs
index 43608ca8358d7..d8af8c148db6f 100644
--- a/dotnet/src/webdriver/DetachedShadowRootException.cs
+++ b/dotnet/src/webdriver/DetachedShadowRootException.cs
@@ -57,17 +57,5 @@ public DetachedShadowRootException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected DetachedShadowRootException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/DevTools/DevToolsSession.cs b/dotnet/src/webdriver/DevTools/DevToolsSession.cs
index 860074ecd2848..931fb22dce019 100644
--- a/dotnet/src/webdriver/DevTools/DevToolsSession.cs
+++ b/dotnet/src/webdriver/DevTools/DevToolsSession.cs
@@ -16,6 +16,7 @@
// limitations under the License.
//
+using OpenQA.Selenium.Internal.Logging;
using System;
using System.Collections.Concurrent;
using System.Globalization;
@@ -56,6 +57,8 @@ public class DevToolsSession : IDevToolsSession
private DevToolsDomains domains;
private readonly DevToolsOptions options;
+ private readonly static ILogger logger = Internal.Logging.Log.GetLogger();
+
///
/// Initializes a new instance of the DevToolsSession class, using the specified WebSocket endpoint.
///
@@ -272,6 +275,11 @@ public T GetVersionSpecificDomains() where T : DevToolsSessionDomains
if (this.connection != null && this.connection.IsActive)
{
+ if (logger.IsEnabled(LogEventLevel.Trace))
+ {
+ logger.Trace($"CDP SND >> {message.CommandId} {message.CommandName}: {commandParameters.ToJsonString()}");
+ }
+
LogTrace("Sending {0} {1}: {2}", message.CommandId, message.CommandName, commandParameters.ToString());
string contents = JsonSerializer.Serialize(message);
@@ -540,6 +548,11 @@ private void MonitorMessageQueue()
private void ProcessMessage(string message)
{
+ if (logger.IsEnabled(LogEventLevel.Trace))
+ {
+ logger.Trace($"CDP RCV << {message}");
+ }
+
var messageObject = JsonObject.Parse(message).AsObject();
if (messageObject.TryGetPropertyValue("id", out var idProperty))
@@ -583,7 +596,22 @@ private void ProcessMessage(string message)
// DevTools commands that may be sent in the body of the attached
// event handler. If thread pool starvation seems to become a problem,
// we can switch to a channel-based queue.
- Task.Run(() => OnDevToolsEventReceived(new DevToolsEventReceivedEventArgs(methodParts[0], methodParts[1], eventData)));
+ Task.Run(() =>
+ {
+ try
+ {
+ OnDevToolsEventReceived(new DevToolsEventReceivedEventArgs(methodParts[0], methodParts[1], eventData));
+ }
+ catch (Exception ex)
+ {
+ if (logger.IsEnabled(LogEventLevel.Warn))
+ {
+ logger.Warn($"CDP VNT ^^ Unhandled error occured in event handler of '{method}' method. {ex}");
+ }
+
+ throw;
+ }
+ });
return;
}
diff --git a/dotnet/src/webdriver/DriverServiceNotFoundException.cs b/dotnet/src/webdriver/DriverServiceNotFoundException.cs
index 25ad25cb6d9bc..2094d61cc2761 100644
--- a/dotnet/src/webdriver/DriverServiceNotFoundException.cs
+++ b/dotnet/src/webdriver/DriverServiceNotFoundException.cs
@@ -57,17 +57,5 @@ public DriverServiceNotFoundException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected DriverServiceNotFoundException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/ElementClickInterceptedException.cs b/dotnet/src/webdriver/ElementClickInterceptedException.cs
index 12d073ed66c0f..f937dc5e58af9 100644
--- a/dotnet/src/webdriver/ElementClickInterceptedException.cs
+++ b/dotnet/src/webdriver/ElementClickInterceptedException.cs
@@ -57,17 +57,5 @@ public ElementClickInterceptedException(string message, Exception innerException
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected ElementClickInterceptedException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/ElementNotInteractableException.cs b/dotnet/src/webdriver/ElementNotInteractableException.cs
index f7ccf80ca5e5d..65ae5395e2207 100644
--- a/dotnet/src/webdriver/ElementNotInteractableException.cs
+++ b/dotnet/src/webdriver/ElementNotInteractableException.cs
@@ -57,17 +57,5 @@ public ElementNotInteractableException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected ElementNotInteractableException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/ElementNotSelectableException.cs b/dotnet/src/webdriver/ElementNotSelectableException.cs
index 3574fc6671a77..68fa2b7f4ad6e 100644
--- a/dotnet/src/webdriver/ElementNotSelectableException.cs
+++ b/dotnet/src/webdriver/ElementNotSelectableException.cs
@@ -57,17 +57,5 @@ public ElementNotSelectableException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected ElementNotSelectableException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/ElementNotVisibleException.cs b/dotnet/src/webdriver/ElementNotVisibleException.cs
index 8f27ec01cfa07..43f815ae105ad 100644
--- a/dotnet/src/webdriver/ElementNotVisibleException.cs
+++ b/dotnet/src/webdriver/ElementNotVisibleException.cs
@@ -57,17 +57,5 @@ public ElementNotVisibleException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected ElementNotVisibleException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/InsecureCertificateException.cs b/dotnet/src/webdriver/InsecureCertificateException.cs
index d8cfd9bd71f16..37da31170bf21 100644
--- a/dotnet/src/webdriver/InsecureCertificateException.cs
+++ b/dotnet/src/webdriver/InsecureCertificateException.cs
@@ -57,17 +57,5 @@ public InsecureCertificateException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected InsecureCertificateException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/InvalidCookieDomainException.cs b/dotnet/src/webdriver/InvalidCookieDomainException.cs
index 3e6debb2d72a7..0b9294bb6d5bd 100644
--- a/dotnet/src/webdriver/InvalidCookieDomainException.cs
+++ b/dotnet/src/webdriver/InvalidCookieDomainException.cs
@@ -57,17 +57,5 @@ public InvalidCookieDomainException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected InvalidCookieDomainException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/InvalidElementStateException.cs b/dotnet/src/webdriver/InvalidElementStateException.cs
index f0f2fed91c3a3..1010a9f084d8f 100644
--- a/dotnet/src/webdriver/InvalidElementStateException.cs
+++ b/dotnet/src/webdriver/InvalidElementStateException.cs
@@ -57,17 +57,5 @@ public InvalidElementStateException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected InvalidElementStateException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/InvalidSelectorException.cs b/dotnet/src/webdriver/InvalidSelectorException.cs
index 9cdf2c3e66f17..509dc2d74a785 100644
--- a/dotnet/src/webdriver/InvalidSelectorException.cs
+++ b/dotnet/src/webdriver/InvalidSelectorException.cs
@@ -63,18 +63,6 @@ public InvalidSelectorException(string message, Exception innerException)
{
}
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected InvalidSelectorException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
-
///
/// Add information about obtaining additional support from documentation to this exception.
///
diff --git a/dotnet/src/webdriver/JavaScriptException.cs b/dotnet/src/webdriver/JavaScriptException.cs
index b852e904bfc5e..79045e4093c08 100644
--- a/dotnet/src/webdriver/JavaScriptException.cs
+++ b/dotnet/src/webdriver/JavaScriptException.cs
@@ -57,17 +57,5 @@ public JavaScriptException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected JavaScriptException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/MoveTargetOutOfBoundsException.cs b/dotnet/src/webdriver/MoveTargetOutOfBoundsException.cs
index 7394ce444715d..d2cbd4ea1f1c0 100644
--- a/dotnet/src/webdriver/MoveTargetOutOfBoundsException.cs
+++ b/dotnet/src/webdriver/MoveTargetOutOfBoundsException.cs
@@ -58,17 +58,5 @@ public MoveTargetOutOfBoundsException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected MoveTargetOutOfBoundsException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/NoAlertPresentException.cs b/dotnet/src/webdriver/NoAlertPresentException.cs
index 5573c60308fdb..72c9600280e55 100644
--- a/dotnet/src/webdriver/NoAlertPresentException.cs
+++ b/dotnet/src/webdriver/NoAlertPresentException.cs
@@ -57,17 +57,5 @@ public NoAlertPresentException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected NoAlertPresentException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/NoSuchDriverException.cs b/dotnet/src/webdriver/NoSuchDriverException.cs
index c665131741982..9bd0bf09be271 100644
--- a/dotnet/src/webdriver/NoSuchDriverException.cs
+++ b/dotnet/src/webdriver/NoSuchDriverException.cs
@@ -64,18 +64,6 @@ public NoSuchDriverException(string message, Exception innerException)
{
}
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected NoSuchDriverException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
-
///
/// Add information about obtaining additional support from documentation to this exception.
///
diff --git a/dotnet/src/webdriver/NoSuchElementException.cs b/dotnet/src/webdriver/NoSuchElementException.cs
index ab6150212818b..cfce90ed7e621 100644
--- a/dotnet/src/webdriver/NoSuchElementException.cs
+++ b/dotnet/src/webdriver/NoSuchElementException.cs
@@ -64,18 +64,6 @@ public NoSuchElementException(string message, Exception innerException)
{
}
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected NoSuchElementException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
-
///
/// Add information about obtaining additional support from documentation to this exception.
///
diff --git a/dotnet/src/webdriver/NoSuchFrameException.cs b/dotnet/src/webdriver/NoSuchFrameException.cs
index 6cd8d57d0894d..a3c98cef0509e 100644
--- a/dotnet/src/webdriver/NoSuchFrameException.cs
+++ b/dotnet/src/webdriver/NoSuchFrameException.cs
@@ -57,17 +57,5 @@ public NoSuchFrameException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected NoSuchFrameException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/NoSuchShadowRootException.cs b/dotnet/src/webdriver/NoSuchShadowRootException.cs
index e008da17ec11b..32398d6aecfd2 100644
--- a/dotnet/src/webdriver/NoSuchShadowRootException.cs
+++ b/dotnet/src/webdriver/NoSuchShadowRootException.cs
@@ -57,17 +57,5 @@ public NoSuchShadowRootException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected NoSuchShadowRootException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/NoSuchWindowException.cs b/dotnet/src/webdriver/NoSuchWindowException.cs
index a9674d45f881a..410d1878e3076 100644
--- a/dotnet/src/webdriver/NoSuchWindowException.cs
+++ b/dotnet/src/webdriver/NoSuchWindowException.cs
@@ -57,17 +57,5 @@ public NoSuchWindowException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected NoSuchWindowException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/NotFoundException.cs b/dotnet/src/webdriver/NotFoundException.cs
index 1316d83f7c2c1..1621ca93d999e 100644
--- a/dotnet/src/webdriver/NotFoundException.cs
+++ b/dotnet/src/webdriver/NotFoundException.cs
@@ -57,17 +57,5 @@ public NotFoundException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected NotFoundException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/SeleniumManager.cs b/dotnet/src/webdriver/SeleniumManager.cs
index 5e365ede075b6..d57b13f278180 100644
--- a/dotnet/src/webdriver/SeleniumManager.cs
+++ b/dotnet/src/webdriver/SeleniumManager.cs
@@ -24,7 +24,7 @@
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
-using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
namespace OpenQA.Selenium
{
@@ -38,6 +38,8 @@ public static class SeleniumManager
private static readonly string BinaryFullPath = Environment.GetEnvironmentVariable("SE_MANAGER_PATH");
+ private static readonly JsonSerializerOptions _serializerOptions = new() { PropertyNameCaseInsensitive = true, TypeInfoResolver = SeleniumManagerSerializerContext.Default };
+
static SeleniumManager()
{
@@ -86,10 +88,12 @@ public static Dictionary BinaryPaths(string arguments)
argsBuilder.Append(" --debug");
}
- var output = RunCommand(BinaryFullPath, argsBuilder.ToString());
- Dictionary binaryPaths = new Dictionary();
- binaryPaths.Add("browser_path", (string)output["browser_path"]);
- binaryPaths.Add("driver_path", (string)output["driver_path"]);
+ var smCommandResult = RunCommand(BinaryFullPath, argsBuilder.ToString());
+ Dictionary binaryPaths = new()
+ {
+ { "browser_path", smCommandResult.BrowserPath },
+ { "driver_path", smCommandResult.DriverPath }
+ };
if (_logger.IsEnabled(LogEventLevel.Trace))
{
@@ -108,7 +112,7 @@ public static Dictionary BinaryPaths(string arguments)
///
/// the standard output of the execution.
///
- private static JsonNode RunCommand(string fileName, string arguments)
+ private static SeleniumManagerResponse.ResultResponse RunCommand(string fileName, string arguments)
{
Process process = new Process();
process.StartInfo.FileName = BinaryFullPath;
@@ -174,47 +178,76 @@ private static JsonNode RunCommand(string fileName, string arguments)
}
string output = outputBuilder.ToString().Trim();
- JsonNode resultJsonNode;
+
+ SeleniumManagerResponse jsonResponse;
+
try
{
- var deserializedOutput = JsonSerializer.Deserialize>(output);
- resultJsonNode = deserializedOutput["result"];
+ jsonResponse = JsonSerializer.Deserialize(output, _serializerOptions);
}
catch (Exception ex)
{
throw new WebDriverException($"Error deserializing Selenium Manager's response: {output}", ex);
}
- if (resultJsonNode["logs"] is not null)
+ if (jsonResponse.Logs is not null)
{
- var logs = resultJsonNode["logs"];
- foreach (var entry in logs.AsArray())
+ foreach (var entry in jsonResponse.Logs)
{
- switch (entry.GetPropertyName())
+ switch (entry.Level)
{
case "WARN":
if (_logger.IsEnabled(LogEventLevel.Warn))
{
- _logger.Warn(entry.GetValue());
+ _logger.Warn(entry.Message);
}
break;
case "DEBUG":
if (_logger.IsEnabled(LogEventLevel.Debug))
{
- _logger.Debug(entry.GetValue());
+ _logger.Debug(entry.Message);
}
break;
case "INFO":
if (_logger.IsEnabled(LogEventLevel.Info))
{
- _logger.Info(entry.GetValue());
+ _logger.Info(entry.Message);
}
break;
}
}
}
- return resultJsonNode;
+ return jsonResponse.Result;
+ }
+ }
+
+ internal class SeleniumManagerResponse
+ {
+ public IReadOnlyList Logs { get; set; }
+
+ public ResultResponse Result { get; set; }
+
+ public class LogEntryResponse
+ {
+ public string Level { get; set; }
+
+ public string Message { get; set; }
+ }
+
+ public class ResultResponse
+ {
+ [JsonPropertyName("driver_path")]
+ public string DriverPath { get; set; }
+
+ [JsonPropertyName("browser_path")]
+ public string BrowserPath { get; set; }
}
}
+
+ [JsonSerializable(typeof(SeleniumManagerResponse))]
+ internal partial class SeleniumManagerSerializerContext : JsonSerializerContext
+ {
+
+ }
}
diff --git a/dotnet/src/webdriver/StaleElementReferenceException.cs b/dotnet/src/webdriver/StaleElementReferenceException.cs
index a7c7f68de56f4..81f1fdcd97948 100644
--- a/dotnet/src/webdriver/StaleElementReferenceException.cs
+++ b/dotnet/src/webdriver/StaleElementReferenceException.cs
@@ -63,18 +63,6 @@ public StaleElementReferenceException(string message, Exception innerException)
{
}
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected StaleElementReferenceException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
-
///
/// Add information about obtaining additional support from documentation to this exception.
///
diff --git a/dotnet/src/webdriver/UnableToSetCookieException.cs b/dotnet/src/webdriver/UnableToSetCookieException.cs
index 40e873d714bd0..6ff0d1ddcf5e8 100644
--- a/dotnet/src/webdriver/UnableToSetCookieException.cs
+++ b/dotnet/src/webdriver/UnableToSetCookieException.cs
@@ -57,17 +57,5 @@ public UnableToSetCookieException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected UnableToSetCookieException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/UnhandledAlertException.cs b/dotnet/src/webdriver/UnhandledAlertException.cs
index fe2b56c6b3eb0..7126a7326728e 100644
--- a/dotnet/src/webdriver/UnhandledAlertException.cs
+++ b/dotnet/src/webdriver/UnhandledAlertException.cs
@@ -72,18 +72,6 @@ public UnhandledAlertException(string message, Exception innerException)
{
}
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected UnhandledAlertException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
-
///
/// Gets the text of the unhandled alert.
///
@@ -91,17 +79,5 @@ public string AlertText
{
get { return this.alertText; }
}
-
- ///
- /// Populates a SerializationInfo with the data needed to serialize the target object.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- public override void GetObjectData(SerializationInfo info, StreamingContext context)
- {
- base.GetObjectData(info, context);
- }
}
}
diff --git a/dotnet/src/webdriver/WebDriverArgumentException.cs b/dotnet/src/webdriver/WebDriverArgumentException.cs
index fd92a38fd8091..dcb16ca2cf38c 100644
--- a/dotnet/src/webdriver/WebDriverArgumentException.cs
+++ b/dotnet/src/webdriver/WebDriverArgumentException.cs
@@ -57,17 +57,5 @@ public WebDriverArgumentException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected WebDriverArgumentException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/WebDriverException.cs b/dotnet/src/webdriver/WebDriverException.cs
index 9eb7af0816241..787f5feed25c4 100644
--- a/dotnet/src/webdriver/WebDriverException.cs
+++ b/dotnet/src/webdriver/WebDriverException.cs
@@ -67,17 +67,5 @@ public WebDriverException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected WebDriverException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/WebDriverTimeoutException.cs b/dotnet/src/webdriver/WebDriverTimeoutException.cs
index caedb65302a33..eecadbb915523 100644
--- a/dotnet/src/webdriver/WebDriverTimeoutException.cs
+++ b/dotnet/src/webdriver/WebDriverTimeoutException.cs
@@ -57,17 +57,5 @@ public WebDriverTimeoutException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected WebDriverTimeoutException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/src/webdriver/XPathLookupException.cs b/dotnet/src/webdriver/XPathLookupException.cs
index 81d47ac896e3f..eca80a9e29aba 100644
--- a/dotnet/src/webdriver/XPathLookupException.cs
+++ b/dotnet/src/webdriver/XPathLookupException.cs
@@ -57,17 +57,5 @@ public XPathLookupException(string message, Exception innerException)
: base(message, innerException)
{
}
-
- ///
- /// Initializes a new instance of the class with serialized data.
- ///
- /// The that holds the serialized
- /// object data about the exception being thrown.
- /// The that contains contextual
- /// information about the source or destination.
- protected XPathLookupException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
}
}
diff --git a/dotnet/test/common/DevTools/DevToolsNetworkTest.cs b/dotnet/test/common/DevTools/DevToolsNetworkTest.cs
index 36f88c6ebae72..81436eb26178c 100644
--- a/dotnet/test/common/DevTools/DevToolsNetworkTest.cs
+++ b/dotnet/test/common/DevTools/DevToolsNetworkTest.cs
@@ -203,7 +203,6 @@ await domains.Network.Enable(new CurrentCdpVersion.Network.EnableCommandSettings
}
[Test]
- [IgnorePlatform("Windows", "Not working properly")]
[IgnoreBrowser(Selenium.Browser.IE, "IE does not support Chrome DevTools Protocol")]
[IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Chrome DevTools Protocol")]
[IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Chrome DevTools Protocol")]
@@ -232,7 +231,8 @@ await domains.Network.Enable(new CurrentCdpVersion.Network.EnableCommandSettings
var searchResponse = await domains.Network.SearchInResponseBody(new CurrentCdpVersion.Network.SearchInResponseBodyCommandSettings()
{
RequestId = requestIds[0],
- Query = "/",
+ Query = ".*",
+ IsRegex = true
});
Assert.That(searchResponse.Result.Length > 0);
diff --git a/dotnet/test/common/JavascriptEnabledBrowserTest.cs b/dotnet/test/common/JavascriptEnabledBrowserTest.cs
index 390f3120ae044..78c5a8515c3d1 100644
--- a/dotnet/test/common/JavascriptEnabledBrowserTest.cs
+++ b/dotnet/test/common/JavascriptEnabledBrowserTest.cs
@@ -34,6 +34,7 @@ public void DocumentShouldReflectLatestDom()
[Test]
[IgnoreBrowser(Browser.Chrome, "Not working properly in Chrome")]
+ [IgnoreBrowser(Browser.Edge, "Not working properly in Edge")]
public void ShouldWaitForLoadsToCompleteAfterJavascriptCausesANewPageToLoad()
{
driver.Url = formsPage;
@@ -45,6 +46,7 @@ public void ShouldWaitForLoadsToCompleteAfterJavascriptCausesANewPageToLoad()
[Test]
[IgnoreBrowser(Browser.Chrome, "Not working properly in Chrome")]
+ [IgnoreBrowser(Browser.Edge, "Not working properly in Edge")]
public void ShouldBeAbleToFindElementAfterJavascriptCausesANewPageToLoad()
{
driver.Url = formsPage;
diff --git a/java/maven_install.json b/java/maven_install.json
index 34adb920c307a..8be2dd9b44a41 100644
--- a/java/maven_install.json
+++ b/java/maven_install.json
@@ -1,7 +1,7 @@
{
"__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
- "__INPUT_ARTIFACTS_HASH": -809854593,
- "__RESOLVED_ARTIFACTS_HASH": -1445024694,
+ "__INPUT_ARTIFACTS_HASH": -1369959342,
+ "__RESOLVED_ARTIFACTS_HASH": 2051378450,
"conflict_resolution": {
"com.google.code.gson:gson:2.8.9": "com.google.code.gson:gson:2.11.0",
"com.google.errorprone:error_prone_annotations:2.3.2": "com.google.errorprone:error_prone_annotations:2.28.0",
@@ -194,17 +194,17 @@
},
"com.graphql-java:graphql-java": {
"shasums": {
- "jar": "98c63c1bf51876f84a3770573279be4f98bbfc2c86d6b4819c327fa1cbd2b137",
- "sources": "2f8be51261734b4618551724a5730d1e6299a9adda171178027eacdc8010363a"
+ "jar": "8828fef5d8133d3d5ad23cee262a9b3ab4ce95aedf5e3332bb577a9aa7c627e0",
+ "sources": "122d4adc1c1491f86f08f6ba6206aa9f05d14a02cf36c73baad7e1d8034160a7"
},
- "version": "20.2"
+ "version": "22.3"
},
"com.graphql-java:java-dataloader": {
"shasums": {
- "jar": "b9c7d32aef05a2e33dc07c5ce45b713c405b61c6264cb0ed48aac003add3eaa4",
- "sources": "ffecf802d587b05860d8ab116d3c6a8630f8eca5d3b5da783f8dd085e2ca0591"
+ "jar": "08cec84ac76e32b53ea666260f288f10b3731c21c89f9199b109ced2361f78b8",
+ "sources": "cea71f74025c2ca95618113345f888c780a99bf3133621b54007e1babaef2e14"
},
- "version": "3.2.0"
+ "version": "3.3.0"
},
"commons-codec:commons-codec": {
"shasums": {
@@ -900,8 +900,7 @@
],
"com.graphql-java:graphql-java": [
"com.graphql-java:java-dataloader",
- "org.reactivestreams:reactive-streams",
- "org.slf4j:slf4j-api"
+ "org.reactivestreams:reactive-streams"
],
"com.graphql-java:java-dataloader": [
"org.slf4j:slf4j-api"
@@ -1425,16 +1424,18 @@
],
"com.graphql-java:graphql-java": [
"graphql",
+ "graphql.agent.result",
"graphql.analysis",
"graphql.analysis.values",
- "graphql.cachecontrol",
"graphql.collect",
"graphql.com.google.common.base",
"graphql.com.google.common.collect",
"graphql.com.google.common.math",
"graphql.com.google.common.primitives",
"graphql.execution",
+ "graphql.execution.conditional",
"graphql.execution.directives",
+ "graphql.execution.incremental",
"graphql.execution.instrumentation",
"graphql.execution.instrumentation.dataloader",
"graphql.execution.instrumentation.fieldvalidation",
@@ -1444,11 +1445,15 @@
"graphql.execution.preparsed",
"graphql.execution.preparsed.persisted",
"graphql.execution.reactive",
+ "graphql.execution.values",
+ "graphql.execution.values.legacycoercing",
"graphql.extensions",
"graphql.i18n",
+ "graphql.incremental",
"graphql.introspection",
"graphql.language",
"graphql.normalized",
+ "graphql.normalized.incremental",
"graphql.org.antlr.v4.runtime",
"graphql.org.antlr.v4.runtime.atn",
"graphql.org.antlr.v4.runtime.dfa",
@@ -1466,7 +1471,6 @@
"graphql.schema.diff.reporting",
"graphql.schema.diffing",
"graphql.schema.diffing.ana",
- "graphql.schema.diffing.dot",
"graphql.schema.fetching",
"graphql.schema.idl",
"graphql.schema.idl.errors",
@@ -1475,6 +1479,7 @@
"graphql.schema.usage",
"graphql.schema.validation",
"graphql.schema.visibility",
+ "graphql.schema.visitor",
"graphql.util",
"graphql.validation",
"graphql.validation.rules"
@@ -1484,6 +1489,7 @@
"org.dataloader.annotations",
"org.dataloader.impl",
"org.dataloader.registries",
+ "org.dataloader.scheduler",
"org.dataloader.stats",
"org.dataloader.stats.context"
],
diff --git a/java/src/org/openqa/selenium/grid/graphql/BUILD.bazel b/java/src/org/openqa/selenium/grid/graphql/BUILD.bazel
index 7c008441e8699..4fc0d84d5aa70 100644
--- a/java/src/org/openqa/selenium/grid/graphql/BUILD.bazel
+++ b/java/src/org/openqa/selenium/grid/graphql/BUILD.bazel
@@ -21,6 +21,5 @@ java_library(
"//java/src/org/openqa/selenium/remote/http",
artifact("com.google.guava:guava"),
artifact("com.graphql-java:graphql-java"),
- artifact("com.graphql-java:java-dataloader"),
],
)
diff --git a/java/src/org/openqa/selenium/grid/graphql/GraphqlHandler.java b/java/src/org/openqa/selenium/grid/graphql/GraphqlHandler.java
index 28f35c477cbbe..3ab7a7385f701 100644
--- a/java/src/org/openqa/selenium/grid/graphql/GraphqlHandler.java
+++ b/java/src/org/openqa/selenium/grid/graphql/GraphqlHandler.java
@@ -45,6 +45,7 @@
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import org.openqa.selenium.grid.distributor.Distributor;
import org.openqa.selenium.grid.sessionqueue.NewSessionQueue;
@@ -88,7 +89,7 @@ public GraphqlHandler(
new SchemaGenerator()
.makeExecutableSchema(buildTypeDefinitionRegistry(), buildRuntimeWiring());
- Cache cache =
+ Cache> cache =
CacheBuilder.newBuilder().maximumSize(1024).build();
graphQl =
@@ -97,7 +98,10 @@ public GraphqlHandler(
(executionInput, computeFunction) -> {
try {
return cache.get(
- executionInput.getQuery(), () -> computeFunction.apply(executionInput));
+ executionInput.getQuery(),
+ () ->
+ CompletableFuture.supplyAsync(
+ () -> computeFunction.apply(executionInput)));
} catch (ExecutionException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
diff --git a/java/src/org/openqa/selenium/grid/node/config/NodeFlags.java b/java/src/org/openqa/selenium/grid/node/config/NodeFlags.java
index 3d49df5f713b3..800a0798a4e17 100644
--- a/java/src/org/openqa/selenium/grid/node/config/NodeFlags.java
+++ b/java/src/org/openqa/selenium/grid/node/config/NodeFlags.java
@@ -30,7 +30,7 @@
import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_REGISTER_PERIOD;
import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_SESSION_TIMEOUT;
import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_USE_SELENIUM_MANAGER;
-import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_VNC_ENV_VAR;
+import static org.openqa.selenium.grid.node.config.NodeOptions.DEFAULT_VNC_ENV_VARS;
import static org.openqa.selenium.grid.node.config.NodeOptions.NODE_SECTION;
import static org.openqa.selenium.grid.node.config.NodeOptions.OVERRIDE_MAX_SESSIONS;
@@ -202,8 +202,11 @@ public class NodeFlags implements HasRoles {
description =
"Environment variable to check in order to determine if a vnc stream is "
+ "available or not.")
- @ConfigValue(section = NODE_SECTION, name = "vnc-env-var", example = "SE_START_XVFB")
- public String vncEnvVar = DEFAULT_VNC_ENV_VAR;
+ @ConfigValue(
+ section = NODE_SECTION,
+ name = "vnc-env-var",
+ example = "[\"SE_START_XVFB\", \"SE_START_VNC\", \"SE_START_NO_VNC\"]")
+ public List vncEnvVar = DEFAULT_VNC_ENV_VARS;
@Parameter(
names = "--no-vnc-port",
diff --git a/java/src/org/openqa/selenium/grid/node/config/NodeOptions.java b/java/src/org/openqa/selenium/grid/node/config/NodeOptions.java
index 8f7ea2dd68aaa..7317f6e7c8870 100644
--- a/java/src/org/openqa/selenium/grid/node/config/NodeOptions.java
+++ b/java/src/org/openqa/selenium/grid/node/config/NodeOptions.java
@@ -31,6 +31,7 @@
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
@@ -78,7 +79,8 @@ public class NodeOptions {
static final boolean DEFAULT_DETECT_DRIVERS = true;
static final boolean DEFAULT_USE_SELENIUM_MANAGER = false;
static final boolean OVERRIDE_MAX_SESSIONS = false;
- static final String DEFAULT_VNC_ENV_VAR = "SE_START_XVFB";
+ static final List DEFAULT_VNC_ENV_VARS =
+ Arrays.asList("SE_START_XVFB", "SE_START_VNC", "SE_START_NO_VNC");
static final int DEFAULT_NO_VNC_PORT = 7900;
static final int DEFAULT_REGISTER_CYCLE = 10;
static final int DEFAULT_REGISTER_PERIOD = 120;
@@ -286,9 +288,16 @@ public int getDrainAfterSessionCount() {
@VisibleForTesting
boolean isVncEnabled() {
- String vncEnvVar = config.get(NODE_SECTION, "vnc-env-var").orElse(DEFAULT_VNC_ENV_VAR);
+ List vncEnvVars = DEFAULT_VNC_ENV_VARS;
+ if (config.getAll(NODE_SECTION, "vnc-env-var").isPresent()) {
+ vncEnvVars = config.getAll(NODE_SECTION, "vnc-env-var").get();
+ }
if (!vncEnabledValueSet.getAndSet(true)) {
- vncEnabled.set(Boolean.parseBoolean(System.getenv(vncEnvVar)));
+ boolean allEnabled =
+ vncEnvVars.stream()
+ .allMatch(
+ env -> "true".equalsIgnoreCase(System.getProperty(env, System.getenv(env))));
+ vncEnabled.set(allEnabled);
}
return vncEnabled.get();
}
diff --git a/java/src/org/openqa/selenium/os/ExternalProcess.java b/java/src/org/openqa/selenium/os/ExternalProcess.java
index b9c05f441a347..2bbb7ceb01b9c 100644
--- a/java/src/org/openqa/selenium/os/ExternalProcess.java
+++ b/java/src/org/openqa/selenium/os/ExternalProcess.java
@@ -317,8 +317,11 @@ public void shutdown() {
*/
public void shutdown(Duration timeout) {
try {
- if (process.supportsNormalTermination()) {
- process.destroy();
+ // use the handle to prevent closing the stdin, stdout, stderr streams
+ ProcessHandle handle = process.toHandle();
+
+ if (handle.supportsNormalTermination()) {
+ handle.destroy();
try {
if (process.waitFor(timeout.toMillis(), MILLISECONDS)) {
@@ -330,7 +333,7 @@ public void shutdown(Duration timeout) {
}
}
- process.destroyForcibly();
+ handle.destroyForcibly();
try {
process.waitFor(timeout.toMillis(), MILLISECONDS);
} catch (InterruptedException ex) {
diff --git a/java/src/org/openqa/selenium/remote/codec/AbstractHttpResponseCodec.java b/java/src/org/openqa/selenium/remote/codec/AbstractHttpResponseCodec.java
index b806dfc40935c..78b18f76e09d3 100644
--- a/java/src/org/openqa/selenium/remote/codec/AbstractHttpResponseCodec.java
+++ b/java/src/org/openqa/selenium/remote/codec/AbstractHttpResponseCodec.java
@@ -108,13 +108,6 @@ public Response decode(HttpResponse encodedResponse) {
response.setValue(content);
}
- if (response.getValue() instanceof String) {
- // We normalise to \n because Java will translate this to \r\n
- // if this is suitable on our platform, and if we have \r\n, java will
- // turn this into \r\r\n, which would be Bad!
- response.setValue(((String) response.getValue()).replace("\r\n", "\n"));
- }
-
if (response.getStatus() != null && response.getState() == null) {
response.setState(errorCodes.toState(response.getStatus()));
} else if (response.getStatus() == null && response.getState() != null) {
diff --git a/java/src/org/openqa/selenium/remote/service/DriverFinder.java b/java/src/org/openqa/selenium/remote/service/DriverFinder.java
index 7216b5d9783d1..436878fbbb48f 100644
--- a/java/src/org/openqa/selenium/remote/service/DriverFinder.java
+++ b/java/src/org/openqa/selenium/remote/service/DriverFinder.java
@@ -156,6 +156,8 @@ private List toArguments() {
arguments.add(proxy.getSslProxy());
} else if (proxy.getHttpProxy() != null) {
arguments.add(proxy.getHttpProxy());
+ } else if (proxy.getProxyAutoconfigUrl() != null) {
+ arguments.add(proxy.getProxyAutoconfigUrl());
}
}
return arguments;
diff --git a/java/test/org/openqa/selenium/grid/node/config/NodeOptionsTest.java b/java/test/org/openqa/selenium/grid/node/config/NodeOptionsTest.java
index 43d413cdab362..69f1ba30a9ee9 100644
--- a/java/test/org/openqa/selenium/grid/node/config/NodeOptionsTest.java
+++ b/java/test/org/openqa/selenium/grid/node/config/NodeOptionsTest.java
@@ -719,6 +719,50 @@ void settingSlotMatcherAvailable() {
assertThat(nodeOptions.getSlotMatcher()).isExactlyInstanceOf(YesSlotMatcher.class);
}
+ @Test
+ void testIsVncEnabledAcceptListEnvVarsAndReturnTrue() {
+ System.setProperty("SE_START_XVFB", "true");
+ System.setProperty("SE_START_VNC", "true");
+ System.setProperty("SE_START_NO_VNC", "true");
+ String[] rawConfig =
+ new String[] {
+ "[node]", "vnc-env-var = [\"SE_START_XVFB\", \"SE_START_VNC\", \"SE_START_NO_VNC\"]",
+ };
+ Config config = new TomlConfig(new StringReader(String.join("\n", rawConfig)));
+ NodeOptions nodeOptionsEnabled = new NodeOptions(config);
+ assertThat(config.getAll("node", "vnc-env-var").get())
+ .containsExactly("SE_START_XVFB", "SE_START_VNC", "SE_START_NO_VNC");
+ assertThat(nodeOptionsEnabled.isVncEnabled()).isTrue();
+ }
+
+ @Test
+ void testIsVncEnabledAcceptListEnvVarsAndReturnFalse() {
+ System.setProperty("SE_START_XVFB", "true");
+ System.setProperty("SE_START_VNC", "false");
+ String[] rawConfig =
+ new String[] {
+ "[node]", "vnc-env-var = [\"SE_START_XVFB\", \"SE_START_VNC\", \"SE_START_NO_VNC\"]",
+ };
+ Config config = new TomlConfig(new StringReader(String.join("\n", rawConfig)));
+ NodeOptions nodeOptionsEnabled = new NodeOptions(config);
+ assertThat(config.getAll("node", "vnc-env-var").get())
+ .containsExactly("SE_START_XVFB", "SE_START_VNC", "SE_START_NO_VNC");
+ assertThat(nodeOptionsEnabled.isVncEnabled()).isFalse();
+ }
+
+ @Test
+ void testIsVncEnabledAcceptSingleEnvVar() {
+ System.setProperty("SE_START_XVFB", "false");
+ String[] rawConfig =
+ new String[] {
+ "[node]", "vnc-env-var = \"SE_START_XVFB\"",
+ };
+ Config config = new TomlConfig(new StringReader(String.join("\n", rawConfig)));
+ NodeOptions nodeOptionsEnabled = new NodeOptions(config);
+ assertThat(config.getAll("node", "vnc-env-var").get()).containsExactly("SE_START_XVFB");
+ assertThat(nodeOptionsEnabled.isVncEnabled()).isFalse();
+ }
+
private Condition super List extends Capabilities>> supporting(String name) {
return new Condition<>(
caps -> caps.stream().anyMatch(cap -> name.equals(cap.getBrowserName())),
diff --git a/javascript/grid-ui/src/components/Node/Node.tsx b/javascript/grid-ui/src/components/Node/Node.tsx
index f61b2ec550288..79b2ced05f2a9 100644
--- a/javascript/grid-ui/src/components/Node/Node.tsx
+++ b/javascript/grid-ui/src/components/Node/Node.tsx
@@ -24,29 +24,28 @@ import OsLogo from '../common/OsLogo'
function Node (props) {
const { node } = props
- const nodeStatusDown = node.status === 'DOWN'
+ const getCardStyle = (status: string) => ({
+ height: '100%',
+ flexGrow: 1,
+ opacity: status === 'DOWN' ? 0.25 : 1,
+ bgcolor: (status === 'DOWN' || status === 'DRAINING') ? 'grey.A100' : ''
+ })
return (
-
+
-
+
URI:
{node.uri}
@@ -54,19 +53,19 @@ function Node (props) {
-
-
+
+
-
+
-
+
diff --git a/javascript/grid-ui/src/screens/Overview/Overview.tsx b/javascript/grid-ui/src/screens/Overview/Overview.tsx
index c51b10140305a..4ff654adad294 100644
--- a/javascript/grid-ui/src/screens/Overview/Overview.tsx
+++ b/javascript/grid-ui/src/screens/Overview/Overview.tsx
@@ -19,6 +19,16 @@ import Grid from '@mui/material/Grid'
import Paper from '@mui/material/Paper'
import { loader } from 'graphql.macro'
import React from 'react'
+import { useState, useEffect, useMemo } from 'react'
+import {
+ Box,
+ Checkbox,
+ FormControl,
+ FormControlLabel,
+ InputLabel,
+ MenuItem,
+ Select
+} from '@mui/material'
import Node from '../../components/Node/Node'
import { useQuery } from '@apollo/client'
import NodeInfo from '../../models/node-info'
@@ -30,7 +40,7 @@ import StereotypeInfo from '../../models/stereotype-info'
import browserVersion from '../../util/browser-version'
import Capabilities from '../../models/capabilities'
import { GridConfig } from '../../config'
-import {NODES_QUERY} from "../../graphql/nodes";
+import { NODES_QUERY } from '../../graphql/nodes'
function Overview (): JSX.Element {
const { loading, error, data } = useQuery(NODES_QUERY, {
@@ -38,12 +48,85 @@ function Overview (): JSX.Element {
fetchPolicy: 'network-only'
})
+ const [sortOption, setSortOption] = useState('osInfo.name')
+ const [sortOrder, setSortOrder] = useState(1)
+ const [sortedNodes, setSortedNodes] = useState([])
+ const [isDescending, setIsDescending] = useState(false)
+
+ const handleSortChange = (event: React.ChangeEvent<{ value: unknown }>) => {
+ setSortOption(event.target.value as string)
+ }
+
+ const handleOrderChange = (event: React.ChangeEvent) => {
+ setIsDescending(event.target.checked)
+ setSortOrder(event.target.checked ? -1 : 1)
+ }
+
+ const sortProperties = {
+ 'osInfo.name': (a, b) => a.osInfo.name.localeCompare(b.osInfo.name),
+ 'status': (a, b) => a.status.localeCompare(b.status),
+ 'id': (a, b) => (a.id < b.id ? -1 : 1)
+ }
+
+ const sortNodes = useMemo(() => {
+ return (nodes: NodeInfo[], option: string, order: number) => {
+ const sortFn = sortProperties[option] || (() => 0)
+ return nodes.sort((a, b) => order * sortFn(a, b))
+ }
+ }, [sortOption, sortOrder])
+
+ useEffect(() => {
+ if (data) {
+ const unSortedNodes = data.nodesInfo.nodes.map((node) => {
+ const osInfo: OsInfo = {
+ name: node.osInfo.name,
+ version: node.osInfo.version,
+ arch: node.osInfo.arch
+ }
+
+ interface StereoTypeData {
+ stereotype: Capabilities;
+ slots: number;
+ }
+
+ const slotStereotypes = (JSON.parse(
+ node.stereotypes) as StereoTypeData[]).map((item) => {
+ const slotStereotype: StereotypeInfo = {
+ browserName: item.stereotype.browserName ?? '',
+ browserVersion: browserVersion(
+ item.stereotype.browserVersion ?? item.stereotype.version),
+ platformName: (item.stereotype.platformName
+ ?? item.stereotype.platform) ?? '',
+ slotCount: item.slots,
+ rawData: item
+ }
+ return slotStereotype
+ })
+
+ const newNode: NodeInfo = {
+ uri: node.uri,
+ id: node.id,
+ status: node.status,
+ maxSession: node.maxSession,
+ slotCount: node.slotCount,
+ version: node.version,
+ osInfo: osInfo,
+ sessionCount: node.sessionCount ?? 0,
+ slotStereotypes: slotStereotypes
+ }
+ return newNode
+ })
+
+ setSortedNodes(sortNodes(unSortedNodes, sortOption, sortOrder))
+ }
+ }, [data, sortOption, sortOrder])
+
if (error !== undefined) {
const message = 'There has been an error while loading the Nodes from the Grid.'
const errorMessage = error?.networkError?.message
return (
-
+
)
}
@@ -51,64 +134,43 @@ function Overview (): JSX.Element {
if (loading) {
return (
-
+
)
}
- const unSortedNodes = data.nodesInfo.nodes.map((node) => {
- const osInfo: OsInfo = {
- name: node.osInfo.name,
- version: node.osInfo.version,
- arch: node.osInfo.arch
- }
-
- interface StereoTypeData {
- stereotype: Capabilities
- slots: number
- }
-
- const slotStereotypes = (JSON.parse(
- node.stereotypes) as StereoTypeData[]).map((item) => {
- const slotStereotype: StereotypeInfo = {
- browserName: item.stereotype.browserName ?? '',
- browserVersion: browserVersion(
- item.stereotype.browserVersion ?? item.stereotype.version),
- platformName: (item.stereotype.platformName ??
- item.stereotype.platform) ?? '',
- slotCount: item.slots,
- rawData: item
- }
- return slotStereotype
- })
- const newNode: NodeInfo = {
- uri: node.uri,
- id: node.id,
- status: node.status,
- maxSession: node.maxSession,
- slotCount: node.slotCount,
- version: node.version,
- osInfo: osInfo,
- sessionCount: node.sessionCount ?? 0,
- slotStereotypes: slotStereotypes
- }
- return newNode
- })
-
- const nodes = unSortedNodes.sort((a, b) => (a.id < b.id ? -1 : 1))
- if (nodes.length === 0) {
+ if (sortedNodes.length === 0) {
const shortMessage = 'The Grid has no registered Nodes yet.'
return (
-
+
)
}
return (
- {/* Nodes */}
- {nodes.map((node, index) => {
+
+
+ Sort By
+
+
+ }
+ label="Descending"
+ style={{ marginLeft: '8px' }}
+ />
+
+
+
+ {sortedNodes.map((node, index) => {
return (
-
+
)
diff --git a/py/selenium/webdriver/chromium/service.py b/py/selenium/webdriver/chromium/service.py
index fc7d165f2b8f0..aebedec40f509 100644
--- a/py/selenium/webdriver/chromium/service.py
+++ b/py/selenium/webdriver/chromium/service.py
@@ -39,9 +39,11 @@ def __init__(
service_args: typing.Optional[typing.List[str]] = None,
log_output: SubprocessStdAlias = None,
env: typing.Optional[typing.Mapping[str, str]] = None,
+ driver_path_env_key: str = None,
**kwargs,
) -> None:
self.service_args = service_args or []
+ driver_path_env_key = driver_path_env_key or "SE_CHROMEDRIVER"
if isinstance(log_output, str):
self.service_args.append(f"--log-path={log_output}")
@@ -56,6 +58,7 @@ def __init__(
port=port,
env=env,
log_output=self.log_output,
+ driver_path_env_key=driver_path_env_key,
**kwargs,
)
diff --git a/py/selenium/webdriver/chromium/webdriver.py b/py/selenium/webdriver/chromium/webdriver.py
index 6fc3adeb4086d..d7bf5c706a453 100644
--- a/py/selenium/webdriver/chromium/webdriver.py
+++ b/py/selenium/webdriver/chromium/webdriver.py
@@ -51,7 +51,7 @@ def __init__(
options.binary_location = finder.get_browser_path()
options.browser_version = None
- self.service.path = finder.get_driver_path()
+ self.service.path = self.service.env_path() or finder.get_driver_path()
self.service.start()
executor = ChromiumRemoteConnection(
diff --git a/py/selenium/webdriver/common/bidi/script.py b/py/selenium/webdriver/common/bidi/script.py
index 88a26b6437ca2..6819a5cf63436 100644
--- a/py/selenium/webdriver/common/bidi/script.py
+++ b/py/selenium/webdriver/common/bidi/script.py
@@ -64,7 +64,6 @@ class LogEntryAdded:
@classmethod
def from_json(cls, json):
- print(json)
if json["type"] == "console":
return ConsoleLogEntry.from_json(json)
elif json["type"] == "javascript":
diff --git a/py/selenium/webdriver/common/service.py b/py/selenium/webdriver/common/service.py
index 829e4f43ad967..3afbced1a3e88 100644
--- a/py/selenium/webdriver/common/service.py
+++ b/py/selenium/webdriver/common/service.py
@@ -25,6 +25,7 @@
from platform import system
from subprocess import PIPE
from time import sleep
+from typing import Optional
from typing import cast
from urllib import request
from urllib.error import URLError
@@ -53,6 +54,7 @@ def __init__(
port: int = 0,
log_output: SubprocessStdAlias = None,
env: typing.Optional[typing.Mapping[typing.Any, typing.Any]] = None,
+ driver_path_env_key: str = None,
**kwargs,
) -> None:
if isinstance(log_output, str):
@@ -64,12 +66,13 @@ def __init__(
else:
self.log_output = log_output
- self._path = executable_path
self.port = port or utils.free_port()
# Default value for every python subprocess: subprocess.Popen(..., creationflags=0)
self.popen_kw = kwargs.pop("popen_kw", {})
self.creation_flags = self.popen_kw.pop("creation_flags", 0)
self.env = env or os.environ
+ self.DRIVER_PATH_ENV_KEY = driver_path_env_key
+ self._path = self.env_path() or executable_path
@property
def service_url(self) -> str:
@@ -236,3 +239,6 @@ def _start_process(self, path: str) -> None:
f"'{os.path.basename(self._path)}' executable may have wrong permissions."
) from err
raise
+
+ def env_path(self) -> Optional[str]:
+ return os.getenv(self.DRIVER_PATH_ENV_KEY, None)
diff --git a/py/selenium/webdriver/edge/service.py b/py/selenium/webdriver/edge/service.py
index 7a9142ce75467..b2c7f02faf4f7 100644
--- a/py/selenium/webdriver/edge/service.py
+++ b/py/selenium/webdriver/edge/service.py
@@ -26,8 +26,6 @@ class Service(service.ChromiumService):
:param executable_path: install path of the msedgedriver executable, defaults to `msedgedriver`.
:param port: Port for the service to run on, defaults to 0 where the operating system will decide.
- :param verbose: (Deprecated) Whether to make the webdriver more verbose (passes the --verbose option to the binary).
- Defaults to False.
:param log_output: (Optional) int representation of STDOUT/DEVNULL, any IO instance or String path to file.
:param service_args: (Optional) List of args to be passed to the subprocess when launching the executable.
:param env: (Optional) Mapping of environment variables for the new process, defaults to `os.environ`.
@@ -40,9 +38,11 @@ def __init__(
log_output: SubprocessStdAlias = None,
service_args: typing.Optional[typing.List[str]] = None,
env: typing.Optional[typing.Mapping[str, str]] = None,
+ driver_path_env_key: str = None,
**kwargs,
) -> None:
self.service_args = service_args or []
+ driver_path_env_key = driver_path_env_key or "SE_EDGEDRIVER"
super().__init__(
executable_path=executable_path,
@@ -50,5 +50,6 @@ def __init__(
service_args=service_args,
log_output=log_output,
env=env,
+ driver_path_env_key=driver_path_env_key,
**kwargs,
)
diff --git a/py/selenium/webdriver/firefox/service.py b/py/selenium/webdriver/firefox/service.py
index 4b25cc7b5304d..e34431480547e 100644
--- a/py/selenium/webdriver/firefox/service.py
+++ b/py/selenium/webdriver/firefox/service.py
@@ -40,15 +40,18 @@ def __init__(
service_args: typing.Optional[typing.List[str]] = None,
log_output: SubprocessStdAlias = None,
env: typing.Optional[typing.Mapping[str, str]] = None,
+ driver_path_env_key: str = None,
**kwargs,
) -> None:
self.service_args = service_args or []
+ driver_path_env_key = driver_path_env_key or "SE_GECKODRIVER"
super().__init__(
executable_path=executable_path,
port=port,
log_output=log_output,
env=env,
+ driver_path_env_key=driver_path_env_key,
**kwargs,
)
diff --git a/py/selenium/webdriver/firefox/webdriver.py b/py/selenium/webdriver/firefox/webdriver.py
index c04c2d4a7b458..dd686d93923cc 100644
--- a/py/selenium/webdriver/firefox/webdriver.py
+++ b/py/selenium/webdriver/firefox/webdriver.py
@@ -58,7 +58,7 @@ def __init__(
options.binary_location = finder.get_browser_path()
options.browser_version = None
- self.service.path = finder.get_driver_path()
+ self.service.path = self.service.env_path() or finder.get_driver_path()
self.service.start()
executor = FirefoxRemoteConnection(
diff --git a/py/selenium/webdriver/ie/service.py b/py/selenium/webdriver/ie/service.py
index 4b0d7f0f3bd90..b8fac381d9f35 100644
--- a/py/selenium/webdriver/ie/service.py
+++ b/py/selenium/webdriver/ie/service.py
@@ -32,6 +32,7 @@ def __init__(
service_args: typing.Optional[typing.List[str]] = None,
log_level: typing.Optional[str] = None,
log_output: SubprocessStdAlias = None,
+ driver_path_env_key: str = None,
**kwargs,
) -> None:
"""Creates a new instance of the Service.
@@ -46,6 +47,8 @@ def __init__(
Default is "stdout".
"""
self.service_args = service_args or []
+ driver_path_env_key = driver_path_env_key or "SE_IEDRIVER"
+
if host:
self.service_args.append(f"--host={host}")
if log_level:
@@ -55,6 +58,7 @@ def __init__(
executable_path=executable_path,
port=port,
log_output=log_output,
+ driver_path_env_key=driver_path_env_key,
**kwargs,
)
diff --git a/py/selenium/webdriver/ie/webdriver.py b/py/selenium/webdriver/ie/webdriver.py
index 64bf79fe250dc..11c137e509fe7 100644
--- a/py/selenium/webdriver/ie/webdriver.py
+++ b/py/selenium/webdriver/ie/webdriver.py
@@ -46,7 +46,7 @@ def __init__(
self.service = service if service else Service()
options = options if options else Options()
- self.service.path = DriverFinder(self.service, options).get_driver_path()
+ self.service.path = self.service.env_path() or DriverFinder(self.service, options).get_driver_path()
self.service.start()
executor = RemoteConnection(
diff --git a/py/selenium/webdriver/safari/service.py b/py/selenium/webdriver/safari/service.py
index 9728b877720c4..3386198c893ac 100644
--- a/py/selenium/webdriver/safari/service.py
+++ b/py/selenium/webdriver/safari/service.py
@@ -37,15 +37,18 @@ def __init__(
service_args: typing.Optional[typing.List[str]] = None,
env: typing.Optional[typing.Mapping[str, str]] = None,
reuse_service=False,
+ driver_path_env_key: str = None,
**kwargs,
) -> None:
self.service_args = service_args or []
+ driver_path_env_key = driver_path_env_key or "SE_SAFARIDRIVER"
self.reuse_service = reuse_service
super().__init__(
executable_path=executable_path,
port=port,
env=env,
+ driver_path_env_key=driver_path_env_key,
**kwargs,
)
diff --git a/py/selenium/webdriver/safari/webdriver.py b/py/selenium/webdriver/safari/webdriver.py
index 259b2d82047bf..2c37a4bd7abde 100644
--- a/py/selenium/webdriver/safari/webdriver.py
+++ b/py/selenium/webdriver/safari/webdriver.py
@@ -45,7 +45,7 @@ def __init__(
self.service = service if service else Service()
options = options if options else Options()
- self.service.path = DriverFinder(self.service, options).get_driver_path()
+ self.service.path = self.service.env_path() or DriverFinder(self.service, options).get_driver_path()
if not self.service.reuse_service:
self.service.start()
diff --git a/py/test/selenium/webdriver/chrome/chrome_service_tests.py b/py/test/selenium/webdriver/chrome/chrome_service_tests.py
index 51c0252141773..c3b3360c939c4 100644
--- a/py/test/selenium/webdriver/chrome/chrome_service_tests.py
+++ b/py/test/selenium/webdriver/chrome/chrome_service_tests.py
@@ -95,3 +95,29 @@ def test_log_output_null_default(driver, capfd) -> None:
out, err = capfd.readouterr()
assert "Starting ChromeDriver" not in out
driver.quit()
+
+
+@pytest.fixture
+def service():
+ return Service()
+
+
+@pytest.mark.usefixtures("service")
+class TestChromeDriverService:
+ service_path = "/path/to/chromedriver"
+
+ @pytest.fixture(autouse=True)
+ def setup_and_teardown(self):
+ os.environ["SE_CHROMEDRIVER"] = self.service_path
+ yield
+ os.environ.pop("SE_CHROMEDRIVER", None)
+
+ def test_uses_path_from_env_variable(self, service):
+ assert "chromedriver" in service.path
+
+ def test_updates_path_after_setting_env_variable(self, service):
+ new_path = "/foo/bar"
+ os.environ["SE_CHROMEDRIVER"] = new_path
+ service.executable_path = self.service_path # Simulating the update
+
+ assert "chromedriver" in service.executable_path
diff --git a/py/test/selenium/webdriver/common/position_and_size_tests.py b/py/test/selenium/webdriver/common/position_and_size_tests.py
index 7ef1334a7a0f3..a438c0f5e0c88 100644
--- a/py/test/selenium/webdriver/common/position_and_size_tests.py
+++ b/py/test/selenium/webdriver/common/position_and_size_tests.py
@@ -109,12 +109,6 @@ def test_should_correctly_identify_that_an_element_has_width_and_height(driver,
def _check_location(location, **kwargs):
- try:
- # python 2.x
- expected = kwargs.viewitems()
- actual = location.viewitems()
- except AttributeError:
- # python 3.x
- expected = kwargs.items()
- actual = location.items()
+ expected = kwargs.items()
+ actual = location.items()
assert expected <= actual
diff --git a/py/test/selenium/webdriver/firefox/firefox_service_tests.py b/py/test/selenium/webdriver/firefox/firefox_service_tests.py
index dde881c8141b9..2a57d0341acee 100644
--- a/py/test/selenium/webdriver/firefox/firefox_service_tests.py
+++ b/py/test/selenium/webdriver/firefox/firefox_service_tests.py
@@ -17,6 +17,8 @@
import os
import subprocess
+import pytest
+
from selenium.webdriver import Firefox
from selenium.webdriver.firefox.service import Service
@@ -54,3 +56,29 @@ def test_log_output_as_stdout(capfd) -> None:
out, err = capfd.readouterr()
assert "geckodriver\tINFO\tListening" in out
driver.quit()
+
+
+@pytest.fixture
+def service():
+ return Service()
+
+
+@pytest.mark.usefixtures("service")
+class TestGeckoDriverService:
+ service_path = "/path/to/geckodriver"
+
+ @pytest.fixture(autouse=True)
+ def setup_and_teardown(self):
+ os.environ["SE_GECKODRIVER"] = self.service_path
+ yield
+ os.environ.pop("SE_GECKODRIVER", None)
+
+ def test_uses_path_from_env_variable(self, service):
+ assert "geckodriver" in service.path
+
+ def test_updates_path_after_setting_env_variable(self, service):
+ new_path = "/foo/bar"
+ os.environ["SE_GECKODRIVER"] = new_path
+ service.executable_path = self.service_path # Simulating the update
+
+ assert "geckodriver" in service.executable_path
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index ae050adc125c8..751f092029e6d 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -750,7 +750,10 @@ pub trait SeleniumManager {
// Download browser if necessary
match self.download_browser_if_necessary(&original_browser_version) {
Ok(_) => {}
- Err(err) => self.check_error_with_driver_in_path(&use_driver_in_path, err)?,
+ Err(err) => {
+ self.set_fallback_driver_from_cache(false);
+ self.check_error_with_driver_in_path(&use_driver_in_path, err)?
+ }
}
// With the discovered browser version, discover the proper driver version using online endpoints