From ccc536d17c176f89a67280911a0149bcb0ad9322 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Mon, 17 Mar 2025 01:49:18 -0400 Subject: [PATCH 01/16] [dotnet] [bidi] Simplify usage of `LocalValue` --- .../BiDi/Modules/Script/LocalValue.cs | 229 +++++++++++++++++- .../BiDi/Script/CallFunctionLocalValueTest.cs | 52 ++-- 2 files changed, 253 insertions(+), 28 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index c8597a76bbbe0..20578932ff310 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -17,8 +17,14 @@ // under the License. // +using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using System.Text.Json.Nodes; using System.Text.Json.Serialization; +using System.Text.RegularExpressions; namespace OpenQA.Selenium.BiDi.Modules.Script; @@ -38,22 +44,37 @@ namespace OpenQA.Selenium.BiDi.Modules.Script; [JsonDerivedType(typeof(SetLocalValue), "set")] public abstract record LocalValue { - public static implicit operator LocalValue(int value) { return new NumberLocalValue(value); } - public static implicit operator LocalValue(string? value) { return value is null ? new NullLocalValue() : new StringLocalValue(value); } + public static implicit operator LocalValue(bool? value) { return value is bool b ? (b ? True : False) : Null; } + public static implicit operator LocalValue(int? value) { return value is int i ? Number(i) : Null; } + public static implicit operator LocalValue(double? value) { return value is double d ? Number(d) : Null; } + public static implicit operator LocalValue(string? value) { return value is null ? Null : String(value); } // TODO: Extend converting from types public static LocalValue ConvertFrom(object? value) { switch (value) { - case LocalValue: - return (LocalValue)value; + case LocalValue localValue: + return localValue; + case null: - return new NullLocalValue(); - case int: - return (int)value; - case string: - return (string)value; + return Null; + + case bool b: + return b ? True : False; + + case int i: + return Number(i); + + case double d: + return Number(d); + + case string str: + return String(str); + + case IEnumerable list: + return Array(list.Select(ConvertFrom).ToList()); + case object: { var type = value.GetType(); @@ -67,10 +88,198 @@ public static LocalValue ConvertFrom(object? value) values.Add([property.Name, ConvertFrom(property.GetValue(value))]); } - return new ObjectLocalValue(values); + return Object(values); } } } + + private static readonly BigInteger MaxDouble = new BigInteger(double.MaxValue); + private static readonly BigInteger MinDouble = new BigInteger(double.MinValue); + + public static LocalValue ConvertFrom(JsonNode? node) + { + if (node is null) + { + return Null; + } + + switch (node.GetValueKind()) + { + case System.Text.Json.JsonValueKind.Null: + return Null; + + case System.Text.Json.JsonValueKind.True: + return True; + + case System.Text.Json.JsonValueKind.False: + return False; + + case System.Text.Json.JsonValueKind.String: + return String(node.ToString()); + + case System.Text.Json.JsonValueKind.Number: + { + var numberString = node.ToString(); + + var bigNumber = BigInteger.Parse(numberString); + + if (bigNumber > MaxDouble || bigNumber < MinDouble) + { + return BigInt(bigNumber); + } + + return Number(double.Parse(numberString)); + } + + case System.Text.Json.JsonValueKind.Array: + return Array(node.AsArray().Select(ConvertFrom)); + + case System.Text.Json.JsonValueKind.Object: + var convertedToListForm = node.AsObject().Select(property => new LocalValue[] { String(property.Key), ConvertFrom(property.Value) }).ToList(); + return Object(convertedToListForm); + + default: + throw new InvalidOperationException("Invalid JSON node"); + } + } + + public static ChannelLocalValue Channel(ChannelLocalValue.ChannelProperties options) + { + return new ChannelLocalValue(options); + } + + public static ArrayLocalValue Array(IEnumerable values) + { + return new ArrayLocalValue(values); + } + + public static SetLocalValue Set(HashSet values) + { + return new SetLocalValue(values); + } + + public static ObjectLocalValue Object(IEnumerable> values) + { + return new ObjectLocalValue(values); + } + + public static ObjectLocalValue Object(IDictionary values) + { + var convertedValues = values.Select(pair => new LocalValue[] { new StringLocalValue(pair.Key), pair.Value }).ToList(); + return new ObjectLocalValue(convertedValues); + } + + public static MapLocalValue Map(IEnumerable> values) + { + return new MapLocalValue(values); + } + + public static MapLocalValue Map(IDictionary values) + { + var convertedValues = values.Select(PairToList).ToList(); + return new MapLocalValue(convertedValues); + } + + private static LocalValue[] PairToList(KeyValuePair pair) + { + return [pair.Key, pair.Value]; + } + + public static BigIntLocalValue BigInt(BigInteger value) + { + return new BigIntLocalValue(value.ToString()); + } + + public static DateLocalValue Date(DateTime value) + { + return new DateLocalValue(value.ToString("o")); + } + + public static StringLocalValue String(string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value), $"string value cannot be null, use a {nameof(NullLocalValue)} value instead"); + } + + return new StringLocalValue(value); + } + + public static NumberLocalValue Number(double value) + { + return new NumberLocalValue(value); + } + + public static BooleanLocalValue True { get; } = new BooleanLocalValue(true); + + public static BooleanLocalValue False { get; } = new BooleanLocalValue(false); + + public static NullLocalValue Null { get; } = new NullLocalValue(); + + public static UndefinedLocalValue Undefined { get; } = new UndefinedLocalValue(); + + /// + /// Converts a .NET Regex into a BiDi Regex + /// + /// A .NET Regex. + /// A BiDi Regex. + /// + /// Note that the .NET regular expression engine does not work the same as the JavaScript engine. + /// To minimize the differences between the two engines, it is recommended to enabled the option. + /// + public static RegExpLocalValue Regex(Regex regex) + { + RegexOptions options = regex.Options; + + if (options == RegexOptions.None) + { + return new RegExpLocalValue(new RegExpValue(regex.ToString())); + } + + string flags = string.Empty; + + const RegexOptions NonBacktracking = (RegexOptions)1024; +#if NET8_0_OR_GREATER + Debug.Assert(NonBacktracking == RegexOptions.NonBacktracking); +#endif + + const RegexOptions NonApplicableOptions = RegexOptions.Compiled | NonBacktracking; + + const RegexOptions UnsupportedOptions = + RegexOptions.ExplicitCapture | + RegexOptions.IgnorePatternWhitespace | + RegexOptions.RightToLeft | + RegexOptions.CultureInvariant; + + options &= ~NonApplicableOptions; + + if ((options & UnsupportedOptions) != 0) + { + throw new NotSupportedException($"The selected RegEx options are not supported in BiDi: {options & UnsupportedOptions}"); + } + + if ((options & RegexOptions.IgnoreCase) != 0) + { + flags += "i"; + options = options & ~RegexOptions.IgnoreCase; + } + + if ((options & RegexOptions.Multiline) != 0) + { + options = options & ~RegexOptions.Multiline; + flags += "m"; + } + + if ((options & RegexOptions.Singleline) != 0) + { + options = options & ~RegexOptions.Singleline; + flags += "s"; + } + + Debug.Assert(options == RegexOptions.None); + + return new RegExpLocalValue(new RegExpValue(regex.ToString()) { Flags = flags }); + } } public abstract record PrimitiveProtocolLocalValue : LocalValue; diff --git a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs index 5def3928912e6..e82c281ca8515 100644 --- a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs +++ b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs @@ -27,7 +27,7 @@ class CallFunctionLocalValueTest : BiDiTestFixture [Test] public void CanCallFunctionWithArgumentUndefined() { - var arg = new UndefinedLocalValue(); + var arg = LocalValue.Undefined; Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -43,7 +43,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNull() { - var arg = new NullLocalValue(); + var arg = LocalValue.Null; Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -57,9 +57,9 @@ await context.Script.CallFunctionAsync($$""" } [Test] - public void CanCallFunctionWithArgumentBoolean() + public void CanCallFunctionWithArgumentTrue() { - var arg = new BooleanLocalValue(true); + var arg = LocalValue.True; Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -72,10 +72,26 @@ await context.Script.CallFunctionAsync($$""" }, Throws.Nothing); } + [Test] + public void CanCallFunctionWithArgumentFalse() + { + var arg = LocalValue.False; + Assert.That(async () => + { + await context.Script.CallFunctionAsync($$""" + (arg) => { + if (arg !== false) { + throw new Error("Assert failed: " + arg); + } + } + """, false, new() { Arguments = [arg] }); + }, Throws.Nothing); + } + [Test] public void CanCallFunctionWithArgumentBigInt() { - var arg = new BigIntLocalValue("12345"); + var arg = LocalValue.BigInt(12345); Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -91,7 +107,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentEmptyString() { - var arg = new StringLocalValue(string.Empty); + var arg = LocalValue.String(string.Empty); Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -107,7 +123,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNonEmptyString() { - var arg = new StringLocalValue("whoa"); + var arg = LocalValue.String("whoa"); Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -161,7 +177,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNumberFive() { - var arg = new NumberLocalValue(5); + var arg = LocalValue.Number(5); Assert.That(async () => { @@ -178,7 +194,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNumberNegativeFive() { - var arg = new NumberLocalValue(-5); + var arg = LocalValue.Number(-5); Assert.That(async () => { @@ -195,7 +211,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNumberZero() { - var arg = new NumberLocalValue(0); + var arg = LocalValue.Number(0); Assert.That(async () => { @@ -214,7 +230,7 @@ await context.Script.CallFunctionAsync($$""" [IgnoreBrowser(Selenium.Browser.Chrome, "Chromium can't handle -0 argument as a number: https://github.com/w3c/webdriver-bidi/issues/887")] public void CanCallFunctionWithArgumentNumberNegativeZero() { - var arg = new NumberLocalValue(double.NegativeZero); + var arg = LocalValue.Number(double.NegativeZero); Assert.That(async () => { @@ -231,7 +247,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNumberPositiveInfinity() { - var arg = new NumberLocalValue(double.PositiveInfinity); + var arg = LocalValue.Number(double.PositiveInfinity); Assert.That(async () => { @@ -248,7 +264,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNumberNegativeInfinity() { - var arg = new NumberLocalValue(double.NegativeInfinity); + var arg = LocalValue.Number(double.NegativeInfinity); Assert.That(async () => { @@ -265,7 +281,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNumberNaN() { - var arg = new NumberLocalValue(double.NaN); + var arg = LocalValue.Number(double.NaN); Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -298,7 +314,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentArray() { - var arg = new ArrayLocalValue([new StringLocalValue("hi")]); + var arg = LocalValue.Array([new StringLocalValue("hi")]); Assert.That(async () => { @@ -315,7 +331,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentObject() { - var arg = new ObjectLocalValue([[new StringLocalValue("objKey"), new StringLocalValue("objValue")]]); + var arg = LocalValue.Object([[new StringLocalValue("objKey"), new StringLocalValue("objValue")]]); Assert.That(async () => { @@ -332,7 +348,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentMap() { - var arg = new MapLocalValue([[new StringLocalValue("mapKey"), new StringLocalValue("mapValue")]]); + var arg = LocalValue.Map([[new StringLocalValue("mapKey"), new StringLocalValue("mapValue")]]); Assert.That(async () => { @@ -349,7 +365,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentSet() { - var arg = new SetLocalValue([new StringLocalValue("setKey")]); + var arg = LocalValue.Set([new StringLocalValue("setKey")]); Assert.That(async () => { From 0722af5b912a6f9f0e910d3b32a89330ee4d8da1 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Mon, 17 Mar 2025 01:52:08 -0400 Subject: [PATCH 02/16] Update exception --- dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index 20578932ff310..e94316a75d2c4 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -199,7 +199,7 @@ public static StringLocalValue String(string value) { if (value is null) { - throw new ArgumentNullException(nameof(value), $"string value cannot be null, use a {nameof(NullLocalValue)} value instead"); + throw new ArgumentNullException(nameof(value), $"string value cannot be null, use {nameof(LocalValue)}.{nameof(Null)} value instead"); } return new StringLocalValue(value); From cd55951392967cc080fb3faa0d3a9b0391593ed7 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Mon, 17 Mar 2025 01:55:59 -0400 Subject: [PATCH 03/16] Use new methods better in tests --- .../test/common/BiDi/Script/CallFunctionLocalValueTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs index e82c281ca8515..5a56987e7bb11 100644 --- a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs +++ b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs @@ -314,7 +314,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentArray() { - var arg = LocalValue.Array([new StringLocalValue("hi")]); + var arg = LocalValue.Array(["hi"]); Assert.That(async () => { @@ -331,7 +331,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentObject() { - var arg = LocalValue.Object([[new StringLocalValue("objKey"), new StringLocalValue("objValue")]]); + var arg = LocalValue.Object([["objKey", "objValue"]]); Assert.That(async () => { @@ -348,7 +348,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentMap() { - var arg = LocalValue.Map([[new StringLocalValue("mapKey"), new StringLocalValue("mapValue")]]); + var arg = LocalValue.Map([["mapKey", "mapValue"]]); Assert.That(async () => { @@ -365,7 +365,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentSet() { - var arg = LocalValue.Set([new StringLocalValue("setKey")]); + var arg = LocalValue.Set(["setKey"]); Assert.That(async () => { From 47891c57e38affbbd0f22b0b16fe617f4af07af3 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Mon, 17 Mar 2025 09:54:34 -0400 Subject: [PATCH 04/16] `String(null)` returns `NullLocalValue` --- .../src/webdriver/BiDi/Modules/Script/LocalValue.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index e94316a75d2c4..360e5b5b6a14b 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -165,7 +165,7 @@ public static ObjectLocalValue Object(IEnumerable> value public static ObjectLocalValue Object(IDictionary values) { - var convertedValues = values.Select(pair => new LocalValue[] { new StringLocalValue(pair.Key), pair.Value }).ToList(); + var convertedValues = values.Select(PairToList).ToList(); return new ObjectLocalValue(convertedValues); } @@ -180,6 +180,11 @@ public static MapLocalValue Map(IDictionary values) return new MapLocalValue(convertedValues); } + private static LocalValue[] PairToList(KeyValuePair pair) + { + return [String(pair.Key), pair.Value]; + } + private static LocalValue[] PairToList(KeyValuePair pair) { return [pair.Key, pair.Value]; @@ -195,11 +200,11 @@ public static DateLocalValue Date(DateTime value) return new DateLocalValue(value.ToString("o")); } - public static StringLocalValue String(string value) + public static LocalValue String(string? value) { if (value is null) { - throw new ArgumentNullException(nameof(value), $"string value cannot be null, use {nameof(LocalValue)}.{nameof(Null)} value instead"); + return Null; } return new StringLocalValue(value); From c7cbaf7479ad965d4b7642c1d918f80c5e68eb7a Mon Sep 17 00:00:00 2001 From: Michael Render Date: Mon, 17 Mar 2025 12:26:19 -0400 Subject: [PATCH 05/16] Add regex overload that takes a string pattern --- .../webdriver/BiDi/Modules/Script/LocalValue.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index 360e5b5b6a14b..c159448395d9c 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -222,6 +222,15 @@ public static NumberLocalValue Number(double value) public static NullLocalValue Null { get; } = new NullLocalValue(); public static UndefinedLocalValue Undefined { get; } = new UndefinedLocalValue(); + public static RegExpLocalValue Regex(string pattern, string? flags = null) + { + if (pattern is null) + { + throw new ArgumentNullException(nameof(pattern)); + } + + return new RegExpLocalValue(new RegExpValue(pattern) { Flags = flags }); + } /// /// Converts a .NET Regex into a BiDi Regex @@ -234,6 +243,11 @@ public static NumberLocalValue Number(double value) /// public static RegExpLocalValue Regex(Regex regex) { + if (regex is null) + { + throw new ArgumentNullException(nameof(regex)); + } + RegexOptions options = regex.Options; if (options == RegexOptions.None) From 0c4f8bf7943a6a961c3d3572763e206b391e7ab7 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Fri, 21 Mar 2025 11:03:08 -0400 Subject: [PATCH 06/16] Remote static factory methods for now --- .../BiDi/Modules/Script/LocalValue.cs | 201 ++---------------- .../BiDi/Script/CallFunctionLocalValueTest.cs | 36 ++-- 2 files changed, 40 insertions(+), 197 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index c159448395d9c..156ffcf4fb4dc 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -44,10 +44,10 @@ namespace OpenQA.Selenium.BiDi.Modules.Script; [JsonDerivedType(typeof(SetLocalValue), "set")] public abstract record LocalValue { - public static implicit operator LocalValue(bool? value) { return value is bool b ? (b ? True : False) : Null; } - public static implicit operator LocalValue(int? value) { return value is int i ? Number(i) : Null; } - public static implicit operator LocalValue(double? value) { return value is double d ? Number(d) : Null; } - public static implicit operator LocalValue(string? value) { return value is null ? Null : String(value); } + public static implicit operator LocalValue(bool? value) { return value is bool b ? new BooleanLocalValue(b) : new NullLocalValue(); } + public static implicit operator LocalValue(int? value) { return value is int i ? new NumberLocalValue(i) : new NullLocalValue(); } + public static implicit operator LocalValue(double? value) { return value is double d ? new NumberLocalValue(d) : new NullLocalValue(); } + public static implicit operator LocalValue(string? value) { return value is null ? new NullLocalValue() : new StringLocalValue(value); } // TODO: Extend converting from types public static LocalValue ConvertFrom(object? value) @@ -58,22 +58,22 @@ public static LocalValue ConvertFrom(object? value) return localValue; case null: - return Null; + return new NullLocalValue(); case bool b: - return b ? True : False; + return new BooleanLocalValue(b); case int i: - return Number(i); + return new NumberLocalValue(i); case double d: - return Number(d); + return new NumberLocalValue(d); case string str: - return String(str); + return new StringLocalValue(str); case IEnumerable list: - return Array(list.Select(ConvertFrom).ToList()); + return new ArrayLocalValue(list.Select(ConvertFrom).ToList()); case object: { @@ -88,7 +88,7 @@ public static LocalValue ConvertFrom(object? value) values.Add([property.Name, ConvertFrom(property.GetValue(value))]); } - return Object(values); + return new ObjectLocalValue(values); } } } @@ -100,22 +100,22 @@ public static LocalValue ConvertFrom(JsonNode? node) { if (node is null) { - return Null; + return new NullLocalValue(); } switch (node.GetValueKind()) { case System.Text.Json.JsonValueKind.Null: - return Null; + return new NullLocalValue(); case System.Text.Json.JsonValueKind.True: - return True; + return new BooleanLocalValue(true); case System.Text.Json.JsonValueKind.False: - return False; + return new BooleanLocalValue(false); case System.Text.Json.JsonValueKind.String: - return String(node.ToString()); + return new StringLocalValue(node.ToString()); case System.Text.Json.JsonValueKind.Number: { @@ -125,187 +125,30 @@ public static LocalValue ConvertFrom(JsonNode? node) if (bigNumber > MaxDouble || bigNumber < MinDouble) { - return BigInt(bigNumber); + return new BigIntLocalValue(numberString); } - return Number(double.Parse(numberString)); + return new NumberLocalValue(double.Parse(numberString)); } case System.Text.Json.JsonValueKind.Array: - return Array(node.AsArray().Select(ConvertFrom)); + return new ArrayLocalValue(node.AsArray().Select(ConvertFrom)); case System.Text.Json.JsonValueKind.Object: - var convertedToListForm = node.AsObject().Select(property => new LocalValue[] { String(property.Key), ConvertFrom(property.Value) }).ToList(); - return Object(convertedToListForm); + var convertedToListForm = node.AsObject().Select(property => new LocalValue[] { new StringLocalValue(property.Key), ConvertFrom(property.Value) }).ToList(); + return new ObjectLocalValue(convertedToListForm); default: throw new InvalidOperationException("Invalid JSON node"); } } - - public static ChannelLocalValue Channel(ChannelLocalValue.ChannelProperties options) - { - return new ChannelLocalValue(options); - } - - public static ArrayLocalValue Array(IEnumerable values) - { - return new ArrayLocalValue(values); - } - - public static SetLocalValue Set(HashSet values) - { - return new SetLocalValue(values); - } - - public static ObjectLocalValue Object(IEnumerable> values) - { - return new ObjectLocalValue(values); - } - - public static ObjectLocalValue Object(IDictionary values) - { - var convertedValues = values.Select(PairToList).ToList(); - return new ObjectLocalValue(convertedValues); - } - - public static MapLocalValue Map(IEnumerable> values) - { - return new MapLocalValue(values); - } - - public static MapLocalValue Map(IDictionary values) - { - var convertedValues = values.Select(PairToList).ToList(); - return new MapLocalValue(convertedValues); - } - - private static LocalValue[] PairToList(KeyValuePair pair) - { - return [String(pair.Key), pair.Value]; - } - - private static LocalValue[] PairToList(KeyValuePair pair) - { - return [pair.Key, pair.Value]; - } - - public static BigIntLocalValue BigInt(BigInteger value) - { - return new BigIntLocalValue(value.ToString()); - } - - public static DateLocalValue Date(DateTime value) - { - return new DateLocalValue(value.ToString("o")); - } - - public static LocalValue String(string? value) - { - if (value is null) - { - return Null; - } - - return new StringLocalValue(value); - } - - public static NumberLocalValue Number(double value) - { - return new NumberLocalValue(value); - } - - public static BooleanLocalValue True { get; } = new BooleanLocalValue(true); - - public static BooleanLocalValue False { get; } = new BooleanLocalValue(false); - - public static NullLocalValue Null { get; } = new NullLocalValue(); - - public static UndefinedLocalValue Undefined { get; } = new UndefinedLocalValue(); - public static RegExpLocalValue Regex(string pattern, string? flags = null) - { - if (pattern is null) - { - throw new ArgumentNullException(nameof(pattern)); - } - - return new RegExpLocalValue(new RegExpValue(pattern) { Flags = flags }); - } - - /// - /// Converts a .NET Regex into a BiDi Regex - /// - /// A .NET Regex. - /// A BiDi Regex. - /// - /// Note that the .NET regular expression engine does not work the same as the JavaScript engine. - /// To minimize the differences between the two engines, it is recommended to enabled the option. - /// - public static RegExpLocalValue Regex(Regex regex) - { - if (regex is null) - { - throw new ArgumentNullException(nameof(regex)); - } - - RegexOptions options = regex.Options; - - if (options == RegexOptions.None) - { - return new RegExpLocalValue(new RegExpValue(regex.ToString())); - } - - string flags = string.Empty; - - const RegexOptions NonBacktracking = (RegexOptions)1024; -#if NET8_0_OR_GREATER - Debug.Assert(NonBacktracking == RegexOptions.NonBacktracking); -#endif - - const RegexOptions NonApplicableOptions = RegexOptions.Compiled | NonBacktracking; - - const RegexOptions UnsupportedOptions = - RegexOptions.ExplicitCapture | - RegexOptions.IgnorePatternWhitespace | - RegexOptions.RightToLeft | - RegexOptions.CultureInvariant; - - options &= ~NonApplicableOptions; - - if ((options & UnsupportedOptions) != 0) - { - throw new NotSupportedException($"The selected RegEx options are not supported in BiDi: {options & UnsupportedOptions}"); - } - - if ((options & RegexOptions.IgnoreCase) != 0) - { - flags += "i"; - options = options & ~RegexOptions.IgnoreCase; - } - - if ((options & RegexOptions.Multiline) != 0) - { - options = options & ~RegexOptions.Multiline; - flags += "m"; - } - - if ((options & RegexOptions.Singleline) != 0) - { - options = options & ~RegexOptions.Singleline; - flags += "s"; - } - - Debug.Assert(options == RegexOptions.None); - - return new RegExpLocalValue(new RegExpValue(regex.ToString()) { Flags = flags }); - } } public abstract record PrimitiveProtocolLocalValue : LocalValue; public record NumberLocalValue(double Value) : PrimitiveProtocolLocalValue { - public static explicit operator NumberLocalValue(double n) => new NumberLocalValue(n); + public static implicit operator NumberLocalValue(double n) => new NumberLocalValue(n); } public record StringLocalValue(string Value) : PrimitiveProtocolLocalValue; diff --git a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs index 5a56987e7bb11..65599e1a23dc6 100644 --- a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs +++ b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs @@ -27,7 +27,7 @@ class CallFunctionLocalValueTest : BiDiTestFixture [Test] public void CanCallFunctionWithArgumentUndefined() { - var arg = LocalValue.Undefined; + var arg = new UndefinedLocalValue(); Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -43,7 +43,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNull() { - var arg = LocalValue.Null; + var arg = new NullLocalValue(); Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -59,7 +59,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentTrue() { - var arg = LocalValue.True; + var arg = new BooleanLocalValue(true); Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -75,7 +75,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentFalse() { - var arg = LocalValue.False; + var arg = new BooleanLocalValue(false); Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -91,7 +91,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentBigInt() { - var arg = LocalValue.BigInt(12345); + var arg = new BigIntLocalValue("12345"); Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -107,7 +107,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentEmptyString() { - var arg = LocalValue.String(string.Empty); + var arg = new StringLocalValue(string.Empty); Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -123,7 +123,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNonEmptyString() { - var arg = LocalValue.String("whoa"); + var arg = new StringLocalValue("whoa"); Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -177,7 +177,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNumberFive() { - var arg = LocalValue.Number(5); + var arg = new NumberLocalValue(5); Assert.That(async () => { @@ -194,7 +194,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNumberNegativeFive() { - var arg = LocalValue.Number(-5); + var arg = new NumberLocalValue(-5); Assert.That(async () => { @@ -211,7 +211,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNumberZero() { - var arg = LocalValue.Number(0); + var arg = new NumberLocalValue(0); Assert.That(async () => { @@ -230,7 +230,7 @@ await context.Script.CallFunctionAsync($$""" [IgnoreBrowser(Selenium.Browser.Chrome, "Chromium can't handle -0 argument as a number: https://github.com/w3c/webdriver-bidi/issues/887")] public void CanCallFunctionWithArgumentNumberNegativeZero() { - var arg = LocalValue.Number(double.NegativeZero); + var arg = new NumberLocalValue(double.NegativeZero); Assert.That(async () => { @@ -247,7 +247,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNumberPositiveInfinity() { - var arg = LocalValue.Number(double.PositiveInfinity); + var arg = new NumberLocalValue(double.PositiveInfinity); Assert.That(async () => { @@ -264,7 +264,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNumberNegativeInfinity() { - var arg = LocalValue.Number(double.NegativeInfinity); + var arg = new NumberLocalValue(double.NegativeInfinity); Assert.That(async () => { @@ -281,7 +281,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentNumberNaN() { - var arg = LocalValue.Number(double.NaN); + var arg = new NumberLocalValue(double.NaN); Assert.That(async () => { await context.Script.CallFunctionAsync($$""" @@ -314,7 +314,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentArray() { - var arg = LocalValue.Array(["hi"]); + var arg = new ArrayLocalValue(["hi"]); Assert.That(async () => { @@ -331,7 +331,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentObject() { - var arg = LocalValue.Object([["objKey", "objValue"]]); + var arg = new ObjectLocalValue([["objKey", "objValue"]]); Assert.That(async () => { @@ -348,7 +348,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentMap() { - var arg = LocalValue.Map([["mapKey", "mapValue"]]); + var arg = new MapLocalValue([["mapKey", "mapValue"]]); Assert.That(async () => { @@ -365,7 +365,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentSet() { - var arg = LocalValue.Set(["setKey"]); + var arg = new SetLocalValue(["setKey"]); Assert.That(async () => { From 6ae7a94e18b4bea59d8649047fc0ece0ceb186c6 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Fri, 21 Mar 2025 11:07:05 -0400 Subject: [PATCH 07/16] remove unnecessary change --- dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index 156ffcf4fb4dc..e1064271887ac 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -148,7 +148,7 @@ public abstract record PrimitiveProtocolLocalValue : LocalValue; public record NumberLocalValue(double Value) : PrimitiveProtocolLocalValue { - public static implicit operator NumberLocalValue(double n) => new NumberLocalValue(n); + public static explicit operator NumberLocalValue(double n) => new NumberLocalValue(n); } public record StringLocalValue(string Value) : PrimitiveProtocolLocalValue; From c7ac3b9441a941f2f72c137662f682e9b22af720 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Fri, 21 Mar 2025 11:11:12 -0400 Subject: [PATCH 08/16] Avoid BigInt until we need it --- .../src/webdriver/BiDi/Modules/Script/LocalValue.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index e1064271887ac..afa115036aa31 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -93,9 +93,6 @@ public static LocalValue ConvertFrom(object? value) } } - private static readonly BigInteger MaxDouble = new BigInteger(double.MaxValue); - private static readonly BigInteger MinDouble = new BigInteger(double.MinValue); - public static LocalValue ConvertFrom(JsonNode? node) { if (node is null) @@ -121,14 +118,16 @@ public static LocalValue ConvertFrom(JsonNode? node) { var numberString = node.ToString(); - var bigNumber = BigInteger.Parse(numberString); + var numberAsDouble = double.Parse(numberString); - if (bigNumber > MaxDouble || bigNumber < MinDouble) + if (double.IsInfinity(numberAsDouble)) { + // Numbers outside of Int64's range will successfully parse, but become +- Infinity + // We can retain the value using a BigInt return new BigIntLocalValue(numberString); } - return new NumberLocalValue(double.Parse(numberString)); + return new NumberLocalValue(numberAsDouble); } case System.Text.Json.JsonValueKind.Array: From c73fd33d5dd8a846e8086adbb5ff7797ccdc06ae Mon Sep 17 00:00:00 2001 From: Michael Render Date: Fri, 21 Mar 2025 11:11:56 -0400 Subject: [PATCH 09/16] Account for BigInt in ConvertFrom(object) --- dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index afa115036aa31..8a047bed239b5 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -69,6 +69,9 @@ public static LocalValue ConvertFrom(object? value) case double d: return new NumberLocalValue(d); + case BigInteger bigInt: + return new BigIntLocalValue(bigInt.ToString()); + case string str: return new StringLocalValue(str); From a76242548142617855795f3625cb8cfb1f1526de Mon Sep 17 00:00:00 2001 From: Michael Render Date: Fri, 21 Mar 2025 15:50:39 -0400 Subject: [PATCH 10/16] Avoid implicit casts in tests that aren't there to test it --- .../test/common/BiDi/Script/CallFunctionLocalValueTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs index 65599e1a23dc6..68fb29a1a1145 100644 --- a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs +++ b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs @@ -314,7 +314,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentArray() { - var arg = new ArrayLocalValue(["hi"]); + var arg = new ArrayLocalValue([new StringLocalValue("hi")]); Assert.That(async () => { @@ -331,7 +331,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentObject() { - var arg = new ObjectLocalValue([["objKey", "objValue"]]); + var arg = new ObjectLocalValue([[new StringLocalValue("objKey"), new StringLocalValue("objValue")]]); Assert.That(async () => { @@ -348,7 +348,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentMap() { - var arg = new MapLocalValue([["mapKey", "mapValue"]]); + var arg = new MapLocalValue([[new StringLocalValue("mapKey"), new StringLocalValue("mapValue")]]); Assert.That(async () => { @@ -365,7 +365,7 @@ await context.Script.CallFunctionAsync($$""" [Test] public void CanCallFunctionWithArgumentSet() { - var arg = new SetLocalValue(["setKey"]); + var arg = new SetLocalValue([new StringLocalValue("setKey")]); Assert.That(async () => { From 2ae4fad394a79f9ca43fee0a921bcca48148c158 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Tue, 25 Mar 2025 01:26:26 -0400 Subject: [PATCH 11/16] Remote `LocalValue.ConvertFrom(JsonNode)`, expand `ConvertFrom(object)` --- dotnet/src/webdriver/BiDi/BiDiException.cs | 3 + .../BiDi/Modules/Script/LocalValue.cs | 93 +++++++++---------- 2 files changed, 45 insertions(+), 51 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/BiDiException.cs b/dotnet/src/webdriver/BiDi/BiDiException.cs index 412c52e535a79..dc146da8a6bc6 100644 --- a/dotnet/src/webdriver/BiDi/BiDiException.cs +++ b/dotnet/src/webdriver/BiDi/BiDiException.cs @@ -26,4 +26,7 @@ public class BiDiException : Exception public BiDiException(string message) : base(message) { } + public BiDiException(string? message, Exception? innerException) : base(message, innerException) + { + } } diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index 8a047bed239b5..7bb458913548c 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -19,12 +19,9 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Numerics; -using System.Text.Json.Nodes; using System.Text.Json.Serialization; -using System.Text.RegularExpressions; namespace OpenQA.Selenium.BiDi.Modules.Script; @@ -75,73 +72,67 @@ public static LocalValue ConvertFrom(object? value) case string str: return new StringLocalValue(str); - case IEnumerable list: - return new ArrayLocalValue(list.Select(ConvertFrom).ToList()); - - case object: + case IDictionary dictionary: { - var type = value.GetType(); + var bidiObject = new List>(dictionary.Count); - var properties = type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); - - List> values = []; - - foreach (var property in properties) + foreach (var item in dictionary) { - values.Add([property.Name, ConvertFrom(property.GetValue(value))]); + bidiObject.Add([new StringLocalValue(item.Key), ConvertFrom(item.Value)]); } - return new ObjectLocalValue(values); + return new ObjectLocalValue(bidiObject); } - } - } - - public static LocalValue ConvertFrom(JsonNode? node) - { - if (node is null) - { - return new NullLocalValue(); - } - - switch (node.GetValueKind()) - { - case System.Text.Json.JsonValueKind.Null: - return new NullLocalValue(); - case System.Text.Json.JsonValueKind.True: - return new BooleanLocalValue(true); + case IDictionary dictionary: + { + var bidiObject = new List>(dictionary.Count); - case System.Text.Json.JsonValueKind.False: - return new BooleanLocalValue(false); + foreach (var item in dictionary) + { + bidiObject.Add([new StringLocalValue(item.Key), ConvertFrom(item.Value)]); + } - case System.Text.Json.JsonValueKind.String: - return new StringLocalValue(node.ToString()); + return new ObjectLocalValue(bidiObject); + } - case System.Text.Json.JsonValueKind.Number: + case IDictionary dictionary: { - var numberString = node.ToString(); - - var numberAsDouble = double.Parse(numberString); + var bidiObject = new List>(dictionary.Count); - if (double.IsInfinity(numberAsDouble)) + foreach (var item in dictionary) { - // Numbers outside of Int64's range will successfully parse, but become +- Infinity - // We can retain the value using a BigInt - return new BigIntLocalValue(numberString); + bidiObject.Add([ConvertFrom(item.Key), ConvertFrom(item.Value)]); } - return new NumberLocalValue(numberAsDouble); + return new MapLocalValue(bidiObject); } - case System.Text.Json.JsonValueKind.Array: - return new ArrayLocalValue(node.AsArray().Select(ConvertFrom)); + case IEnumerable list: + return new ArrayLocalValue(list.Select(ConvertFrom).ToList()); + + case object: + { + const System.Reflection.BindingFlags Flags = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance; + var properties = value.GetType().GetProperties(Flags); - case System.Text.Json.JsonValueKind.Object: - var convertedToListForm = node.AsObject().Select(property => new LocalValue[] { new StringLocalValue(property.Key), ConvertFrom(property.Value) }).ToList(); - return new ObjectLocalValue(convertedToListForm); + var values = new List>(properties.Length); + foreach (var property in properties) + { + object? propertyValue; + try + { + propertyValue = property.GetValue(value); + } + catch (Exception ex) + { + throw new BiDiException($"Could not retrieve property {property.Name} from {property.DeclaringType}", ex); + } + values.Add([property.Name, ConvertFrom(propertyValue)]); + } - default: - throw new InvalidOperationException("Invalid JSON node"); + return new ObjectLocalValue(values); + } } } } From e875b055c5100b86397d3e251409ef3d988918d5 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Tue, 25 Mar 2025 01:30:35 -0400 Subject: [PATCH 12/16] Add ConvertFrom support for `DateTime` and `long` --- dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index 236c89fe8a498..19be05efaaf18 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -66,6 +66,12 @@ public static LocalValue ConvertFrom(object? value) case double d: return new NumberLocalValue(d); + case long l: + return new NumberLocalValue(l); + + case DateTime dt: + return new DateLocalValue(dt.ToString("o")); + case BigInteger bigInt: return new BigIntLocalValue(bigInt.ToString()); @@ -75,7 +81,6 @@ public static LocalValue ConvertFrom(object? value) case IDictionary dictionary: { var bidiObject = new List>(dictionary.Count); - foreach (var item in dictionary) { bidiObject.Add([new StringLocalValue(item.Key), ConvertFrom(item.Value)]); @@ -87,7 +92,6 @@ public static LocalValue ConvertFrom(object? value) case IDictionary dictionary: { var bidiObject = new List>(dictionary.Count); - foreach (var item in dictionary) { bidiObject.Add([new StringLocalValue(item.Key), ConvertFrom(item.Value)]); @@ -99,7 +103,6 @@ public static LocalValue ConvertFrom(object? value) case IDictionary dictionary: { var bidiObject = new List>(dictionary.Count); - foreach (var item in dictionary) { bidiObject.Add([ConvertFrom(item.Key), ConvertFrom(item.Value)]); @@ -114,6 +117,7 @@ public static LocalValue ConvertFrom(object? value) case object: { const System.Reflection.BindingFlags Flags = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance; + var properties = value.GetType().GetProperties(Flags); var values = new List>(properties.Length); From 4e6db49885b7290dd65321c05852754b5ec806b0 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Tue, 25 Mar 2025 17:14:42 -0400 Subject: [PATCH 13/16] Add unit tests to LocalValue operators --- .../BiDi/Script/CallFunctionLocalValueTest.cs | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs index 68fb29a1a1145..235077d7fef06 100644 --- a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs +++ b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs @@ -378,4 +378,81 @@ await context.Script.CallFunctionAsync($$""" """, false, new() { Arguments = [arg] }); }, Throws.Nothing); } + + [Test] + public void CanConvertNullBoolToLocalValue() + { + bool? arg = null; + LocalValue result = (LocalValue)arg; + Assert.That(result, Is.TypeOf()); + } + + [Test] + public void CanConvertTrueToLocalValue() + { + bool arg = true; + LocalValue result = (LocalValue)arg; + Assert.That(result, Is.TypeOf()); + Assert.That((result as BooleanLocalValue).Value, Is.True); + } + + [Test] + public void CanConvertFalseToLocalValue() + { + bool arg = false; + LocalValue result = (LocalValue)arg; + Assert.That(result, Is.TypeOf()); + Assert.That((result as BooleanLocalValue).Value, Is.False); + } + + [Test] + public void CanConvertNullIntToLocalValue() + { + int? arg = null; + LocalValue result = (LocalValue)arg; + Assert.That(result, Is.TypeOf()); + } + + [Test] + public void CanConvertZeroIntToLocalValue() + { + int arg = 0; + LocalValue result = (LocalValue)arg; + Assert.That(result, Is.TypeOf()); + Assert.That((result as NumberLocalValue).Value, Is.Zero); + } + + [Test] + public void CanConvertNullDoubleToLocalValue() + { + double? arg = null; + LocalValue result = (LocalValue)arg; + Assert.That(result, Is.TypeOf()); + } + + [Test] + public void CanConvertZeroDoubleToLocalValue() + { + double arg = 0; + LocalValue result = (LocalValue)arg; + Assert.That(result, Is.TypeOf()); + Assert.That((result as NumberLocalValue).Value, Is.Zero); + } + + [Test] + public void CanConvertNullStringToLocalValue() + { + string arg = null; + LocalValue result = (LocalValue)arg; + Assert.That(result, Is.TypeOf()); + } + + [Test] + public void CanConvertStringToLocalValue() + { + string arg = "value"; + LocalValue result = (LocalValue)arg; + Assert.That(result, Is.TypeOf()); + Assert.That((result as StringLocalValue).Value, Is.EqualTo(arg)); + } } From efd0058ded396cf09c0fdbfbc8f33b2cabc74bac Mon Sep 17 00:00:00 2001 From: Michael Render Date: Tue, 25 Mar 2025 17:30:44 -0400 Subject: [PATCH 14/16] Use var --- .../BiDi/Script/CallFunctionLocalValueTest.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs index 235077d7fef06..0e7e1ebdbc6cb 100644 --- a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs +++ b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs @@ -383,7 +383,7 @@ await context.Script.CallFunctionAsync($$""" public void CanConvertNullBoolToLocalValue() { bool? arg = null; - LocalValue result = (LocalValue)arg; + var result = (LocalValue)arg; Assert.That(result, Is.TypeOf()); } @@ -391,7 +391,7 @@ public void CanConvertNullBoolToLocalValue() public void CanConvertTrueToLocalValue() { bool arg = true; - LocalValue result = (LocalValue)arg; + var result = (LocalValue)arg; Assert.That(result, Is.TypeOf()); Assert.That((result as BooleanLocalValue).Value, Is.True); } @@ -400,7 +400,7 @@ public void CanConvertTrueToLocalValue() public void CanConvertFalseToLocalValue() { bool arg = false; - LocalValue result = (LocalValue)arg; + var result = (LocalValue)arg; Assert.That(result, Is.TypeOf()); Assert.That((result as BooleanLocalValue).Value, Is.False); } @@ -409,7 +409,7 @@ public void CanConvertFalseToLocalValue() public void CanConvertNullIntToLocalValue() { int? arg = null; - LocalValue result = (LocalValue)arg; + var result = (LocalValue)arg; Assert.That(result, Is.TypeOf()); } @@ -417,7 +417,7 @@ public void CanConvertNullIntToLocalValue() public void CanConvertZeroIntToLocalValue() { int arg = 0; - LocalValue result = (LocalValue)arg; + var result = (LocalValue)arg; Assert.That(result, Is.TypeOf()); Assert.That((result as NumberLocalValue).Value, Is.Zero); } @@ -426,7 +426,7 @@ public void CanConvertZeroIntToLocalValue() public void CanConvertNullDoubleToLocalValue() { double? arg = null; - LocalValue result = (LocalValue)arg; + var result = (LocalValue)arg; Assert.That(result, Is.TypeOf()); } @@ -434,7 +434,7 @@ public void CanConvertNullDoubleToLocalValue() public void CanConvertZeroDoubleToLocalValue() { double arg = 0; - LocalValue result = (LocalValue)arg; + var result = (LocalValue)arg; Assert.That(result, Is.TypeOf()); Assert.That((result as NumberLocalValue).Value, Is.Zero); } @@ -443,7 +443,7 @@ public void CanConvertZeroDoubleToLocalValue() public void CanConvertNullStringToLocalValue() { string arg = null; - LocalValue result = (LocalValue)arg; + var result = (LocalValue)arg; Assert.That(result, Is.TypeOf()); } @@ -451,7 +451,7 @@ public void CanConvertNullStringToLocalValue() public void CanConvertStringToLocalValue() { string arg = "value"; - LocalValue result = (LocalValue)arg; + var result = (LocalValue)arg; Assert.That(result, Is.TypeOf()); Assert.That((result as StringLocalValue).Value, Is.EqualTo(arg)); } From 64c5eb0e3d976ec1d7485a888c1d4c6d21ce491b Mon Sep 17 00:00:00 2001 From: Michael Render Date: Wed, 26 Mar 2025 16:39:30 -0400 Subject: [PATCH 15/16] Use in-line literals for `LocalValue` conversions, use a separate fixture --- .../BiDi/Script/CallFunctionLocalValueTest.cs | 77 -------------- .../BiDi/Script/LocalValueConversionTests.cs | 100 ++++++++++++++++++ 2 files changed, 100 insertions(+), 77 deletions(-) create mode 100644 dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs diff --git a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs index 0e7e1ebdbc6cb..68fb29a1a1145 100644 --- a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs +++ b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs @@ -378,81 +378,4 @@ await context.Script.CallFunctionAsync($$""" """, false, new() { Arguments = [arg] }); }, Throws.Nothing); } - - [Test] - public void CanConvertNullBoolToLocalValue() - { - bool? arg = null; - var result = (LocalValue)arg; - Assert.That(result, Is.TypeOf()); - } - - [Test] - public void CanConvertTrueToLocalValue() - { - bool arg = true; - var result = (LocalValue)arg; - Assert.That(result, Is.TypeOf()); - Assert.That((result as BooleanLocalValue).Value, Is.True); - } - - [Test] - public void CanConvertFalseToLocalValue() - { - bool arg = false; - var result = (LocalValue)arg; - Assert.That(result, Is.TypeOf()); - Assert.That((result as BooleanLocalValue).Value, Is.False); - } - - [Test] - public void CanConvertNullIntToLocalValue() - { - int? arg = null; - var result = (LocalValue)arg; - Assert.That(result, Is.TypeOf()); - } - - [Test] - public void CanConvertZeroIntToLocalValue() - { - int arg = 0; - var result = (LocalValue)arg; - Assert.That(result, Is.TypeOf()); - Assert.That((result as NumberLocalValue).Value, Is.Zero); - } - - [Test] - public void CanConvertNullDoubleToLocalValue() - { - double? arg = null; - var result = (LocalValue)arg; - Assert.That(result, Is.TypeOf()); - } - - [Test] - public void CanConvertZeroDoubleToLocalValue() - { - double arg = 0; - var result = (LocalValue)arg; - Assert.That(result, Is.TypeOf()); - Assert.That((result as NumberLocalValue).Value, Is.Zero); - } - - [Test] - public void CanConvertNullStringToLocalValue() - { - string arg = null; - var result = (LocalValue)arg; - Assert.That(result, Is.TypeOf()); - } - - [Test] - public void CanConvertStringToLocalValue() - { - string arg = "value"; - var result = (LocalValue)arg; - Assert.That(result, Is.TypeOf()); - Assert.That((result as StringLocalValue).Value, Is.EqualTo(arg)); - } } diff --git a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs new file mode 100644 index 0000000000000..522f27c423614 --- /dev/null +++ b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs @@ -0,0 +1,100 @@ +// +// 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 NUnit.Framework; +using OpenQA.Selenium.BiDi.Modules.Script; + +namespace OpenQA.Selenium.BiDi.Script; + +class LocalValueConversionTests +{ + [Test] + public void CanConvertNullBoolToLocalValue() + { + bool? arg = null; + LocalValue result = arg; + Assert.That(result, Is.TypeOf()); + } + + [Test] + public void CanConvertTrueToLocalValue() + { + LocalValue result = true; + Assert.That(result, Is.TypeOf()); + Assert.That((result as BooleanLocalValue).Value, Is.True); + } + + [Test] + public void CanConvertFalseToLocalValue() + { + LocalValue result = false; + Assert.That(result, Is.TypeOf()); + Assert.That((result as BooleanLocalValue).Value, Is.False); + } + + [Test] + public void CanConvertNullIntToLocalValue() + { + int? arg = null; + LocalValue result = arg; + Assert.That(result, Is.TypeOf()); + } + + [Test] + public void CanConvertZeroIntToLocalValue() + { + int arg = 0; + LocalValue result = 0; + Assert.That(result, Is.TypeOf()); + Assert.That((result as NumberLocalValue).Value, Is.Zero); + } + + [Test] + public void CanConvertNullDoubleToLocalValue() + { + double? arg = null; + LocalValue result = arg; + Assert.That(result, Is.TypeOf()); + } + + [Test] + public void CanConvertZeroDoubleToLocalValue() + { + double arg = 0; + LocalValue result = arg; + Assert.That(result, Is.TypeOf()); + Assert.That((result as NumberLocalValue).Value, Is.Zero); + } + + [Test] + public void CanConvertNullStringToLocalValue() + { + string arg = null; + LocalValue result = arg; + Assert.That(result, Is.TypeOf()); + } + + [Test] + public void CanConvertStringToLocalValue() + { + LocalValue result = "value"; + Assert.That(result, Is.TypeOf()); + Assert.That((result as StringLocalValue).Value, Is.EqualTo("value")); + } +} From d0da6e24539b0b369de6ca1efbbf35ead4ef39d1 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Wed, 26 Mar 2025 16:40:41 -0400 Subject: [PATCH 16/16] Use int literal --- dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs index 522f27c423614..c0a1058a55ef5 100644 --- a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs +++ b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs @@ -59,7 +59,6 @@ public void CanConvertNullIntToLocalValue() [Test] public void CanConvertZeroIntToLocalValue() { - int arg = 0; LocalValue result = 0; Assert.That(result, Is.TypeOf()); Assert.That((result as NumberLocalValue).Value, Is.Zero);