From 742b3862830faa6fca888de3998a7cc999cee561 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Fri, 28 Mar 2025 14:33:37 -0400 Subject: [PATCH 01/14] [dotnet] [bidi] Add strongly-typed `LocalValue.ConvertFrom` overloads --- .../BiDi/Modules/Script/LocalValue.cs | 282 ++++++++++++++---- 1 file changed, 219 insertions(+), 63 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index 19be05efaaf18..2f670d4af1031 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -41,10 +41,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 ? 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); } + public static implicit operator LocalValue(bool? value) { return ConvertFrom(value); } + public static implicit operator LocalValue(int? value) { return ConvertFrom(value); } + public static implicit operator LocalValue(double? value) { return ConvertFrom(value); } + public static implicit operator LocalValue(string? value) { return ConvertFrom(value); } // TODO: Extend converting from types public static LocalValue ConvertFrom(object? value) @@ -58,86 +58,242 @@ public static LocalValue ConvertFrom(object? value) return new NullLocalValue(); case bool b: - return new BooleanLocalValue(b); + return ConvertFrom(b); case int i: - return new NumberLocalValue(i); + return ConvertFrom(i); case double d: - return new NumberLocalValue(d); + return ConvertFrom(d); case long l: - return new NumberLocalValue(l); + return ConvertFrom(l); case DateTime dt: - return new DateLocalValue(dt.ToString("o")); + return ConvertFrom(dt); case BigInteger bigInt: - return new BigIntLocalValue(bigInt.ToString()); + return ConvertFrom(bigInt); case string str: - return new StringLocalValue(str); + return ConvertFrom(str); case IDictionary dictionary: - { - var bidiObject = new List>(dictionary.Count); - foreach (var item in dictionary) - { - bidiObject.Add([new StringLocalValue(item.Key), ConvertFrom(item.Value)]); - } - - return new ObjectLocalValue(bidiObject); - } + return ConvertFrom(dictionary); case IDictionary dictionary: - { - var bidiObject = new List>(dictionary.Count); - foreach (var item in dictionary) - { - bidiObject.Add([new StringLocalValue(item.Key), ConvertFrom(item.Value)]); - } - - return new ObjectLocalValue(bidiObject); - } + return ConvertFrom(dictionary); case IDictionary dictionary: - { - var bidiObject = new List>(dictionary.Count); - foreach (var item in dictionary) - { - bidiObject.Add([ConvertFrom(item.Key), ConvertFrom(item.Value)]); - } - - return new MapLocalValue(bidiObject); - } + return ConvertFrom(dictionary); 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); - - 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)]); - } - - return new ObjectLocalValue(values); - } + return ConvertFrom(list); + + default: + return ReflectionBasedConvertFrom(value); + } + } + + public static LocalValue ConvertFrom(bool value) + { + return new BooleanLocalValue(value); + } + + public static LocalValue ConvertFrom(bool? value) + { + if (value is bool b) + { + return new BooleanLocalValue(b); + } + + return new NullLocalValue(); + } + + public static LocalValue ConvertFrom(int value) + { + return new NumberLocalValue(value); + } + + public static LocalValue ConvertFrom(int? value) + { + if (value is int b) + { + return new NumberLocalValue(b); + } + + return new NullLocalValue(); + } + + public static LocalValue ConvertFrom(double value) + { + return new NumberLocalValue(value); + } + + public static LocalValue ConvertFrom(double? value) + { + if (value is double b) + { + return new NumberLocalValue(b); + } + + return new NullLocalValue(); + } + + public static LocalValue ConvertFrom(long value) + { + return new NumberLocalValue(value); + } + + public static LocalValue ConvertFrom(long? value) + { + if (value is long b) + { + return new NumberLocalValue(b); + } + + return new NullLocalValue(); + } + + public static LocalValue ConvertFrom(string? value) + { + if (value is not null) + { + return new StringLocalValue(value); + } + + return new NullLocalValue(); + } + + public static LocalValue ConvertFrom(DateTime dateTime) + { + return new DateLocalValue(dateTime.ToString("o")); + } + + public static LocalValue ConvertFrom(BigInteger bigInt) + { + return new BigIntLocalValue(bigInt.ToString()); + } + + public static LocalValue ConvertFrom(IEnumerable? values) + { + if (values is null) + { + return new NullLocalValue(); } + + LocalValue[] convertedList = values.Select(ConvertFrom).ToArray(); + return new ArrayLocalValue(convertedList); + } + + public static LocalValue ConvertFrom(List? values) + { + if (values is null) + { + return new NullLocalValue(); + } + + List convertedList = values.ConvertAll(ConvertFrom); + return new ArrayLocalValue(convertedList); + } + + public static LocalValue ConvertFrom(object?[]? values) + { + if (values is null) + { + return new NullLocalValue(); + } + + LocalValue[] convertedArray = Array.ConvertAll(values, ConvertFrom); + return new ArrayLocalValue(Array.ConvertAll(values, ConvertFrom)); + } + + public static LocalValue ConvertFrom(IList? values) + { + if (values is null) + { + return new NullLocalValue(); + } + + List convertedList = [.. values.Select(ConvertFrom)]; + return new ArrayLocalValue(convertedList); + } + + public static LocalValue ConvertFrom(IDictionary? dictionary) + { + if (dictionary is null) + { + return new NullLocalValue(); + } + + var bidiObject = new List>(dictionary.Count); + foreach (KeyValuePair item in dictionary) + { + bidiObject.Add([ConvertFrom(item.Key), ConvertFrom(item.Value)]); + } + + return new ObjectLocalValue(bidiObject); + } + + public static LocalValue ConvertFrom(IDictionary? dictionary) + { + if (dictionary is null) + { + return new NullLocalValue(); + } + + var bidiObject = new List>(dictionary.Count); + foreach (KeyValuePair item in dictionary) + { + bidiObject.Add([ConvertFrom(item.Key), ConvertFrom(item.Value)]); + } + + return new ObjectLocalValue(bidiObject); + } + + public static LocalValue ConvertFrom(IDictionary? dictionary) + { + if (dictionary is null) + { + return new NullLocalValue(); + } + + var bidiObject = new List>(dictionary.Count); + foreach (KeyValuePair item in dictionary) + { + bidiObject.Add([ConvertFrom(item.Key), ConvertFrom(item.Value)]); + } + + return new MapLocalValue(bidiObject); + } + + private static LocalValue ReflectionBasedConvertFrom(object? value) + { + if (value is null) + { + return new NullLocalValue(); + } + + const System.Reflection.BindingFlags Flags = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance; + + System.Reflection.PropertyInfo[] properties = value.GetType().GetProperties(Flags); + + var values = new List>(properties.Length); + foreach (System.Reflection.PropertyInfo? 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)]); + } + + return new ObjectLocalValue(values); } } From f4398fcaa44a277ced42a257518095329ee02af2 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Fri, 28 Mar 2025 16:18:25 -0400 Subject: [PATCH 02/14] use generics --- .../BiDi/Modules/Script/LocalValue.cs | 65 ++++++------------- 1 file changed, 19 insertions(+), 46 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index 2f670d4af1031..d58dc1aa152a7 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -87,6 +87,12 @@ public static LocalValue ConvertFrom(object? value) case IDictionary dictionary: return ConvertFrom(dictionary); + case ISet set: + return ConvertFrom(set); + + case IList set: + return ConvertFrom(set); + case IEnumerable list: return ConvertFrom(list); @@ -186,40 +192,23 @@ public static LocalValue ConvertFrom(IEnumerable? values) return new ArrayLocalValue(convertedList); } - public static LocalValue ConvertFrom(List? values) + public static LocalValue ConvertFrom(IList? values) { if (values is null) { return new NullLocalValue(); } - List convertedList = values.ConvertAll(ConvertFrom); + List convertedList = [.. values.Select(element => ConvertFrom(element))]; return new ArrayLocalValue(convertedList); } - public static LocalValue ConvertFrom(object?[]? values) + public static LocalValue ConvertFrom(IDictionary? dictionary) { - if (values is null) - { - return new NullLocalValue(); - } - - LocalValue[] convertedArray = Array.ConvertAll(values, ConvertFrom); - return new ArrayLocalValue(Array.ConvertAll(values, ConvertFrom)); + return ConvertFrom(dictionary); } - public static LocalValue ConvertFrom(IList? values) - { - if (values is null) - { - return new NullLocalValue(); - } - - List convertedList = [.. values.Select(ConvertFrom)]; - return new ArrayLocalValue(convertedList); - } - - public static LocalValue ConvertFrom(IDictionary? dictionary) + public static LocalValue ConvertFrom(IDictionary? dictionary) { if (dictionary is null) { @@ -227,44 +216,28 @@ public static LocalValue ConvertFrom(IDictionary? dictionary) } var bidiObject = new List>(dictionary.Count); - foreach (KeyValuePair item in dictionary) + foreach (KeyValuePair item in dictionary) { bidiObject.Add([ConvertFrom(item.Key), ConvertFrom(item.Value)]); } - return new ObjectLocalValue(bidiObject); - } - - public static LocalValue ConvertFrom(IDictionary? dictionary) - { - if (dictionary is null) - { - return new NullLocalValue(); - } - - var bidiObject = new List>(dictionary.Count); - foreach (KeyValuePair item in dictionary) + if (typeof(TKey) == typeof(string)) { - bidiObject.Add([ConvertFrom(item.Key), ConvertFrom(item.Value)]); + return new ObjectLocalValue(bidiObject); } - return new ObjectLocalValue(bidiObject); + return new MapLocalValue(bidiObject); } - public static LocalValue ConvertFrom(IDictionary? dictionary) + public static LocalValue ConvertFrom(ISet? set) { - if (dictionary is null) + if (set is null) { return new NullLocalValue(); } - var bidiObject = new List>(dictionary.Count); - foreach (KeyValuePair item in dictionary) - { - bidiObject.Add([ConvertFrom(item.Key), ConvertFrom(item.Value)]); - } - - return new MapLocalValue(bidiObject); + LocalValue[] convertedValues = [.. set.Select(x => ConvertFrom(x))]; + return new SetLocalValue(convertedValues); } private static LocalValue ReflectionBasedConvertFrom(object? value) From 1553f627041cfac08d78c863da717e6f18548705 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sat, 12 Apr 2025 13:08:12 +0300 Subject: [PATCH 03/14] Simplify --- .../BiDi/Modules/Script/LocalValue.cs | 73 +++++----- .../BiDi/Script/LocalValueConversionTests.cs | 127 ++++++++++++++---- 2 files changed, 138 insertions(+), 62 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index d58dc1aa152a7..b16d9e799f7e7 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -101,11 +101,6 @@ public static LocalValue ConvertFrom(object? value) } } - public static LocalValue ConvertFrom(bool value) - { - return new BooleanLocalValue(value); - } - public static LocalValue ConvertFrom(bool? value) { if (value is bool b) @@ -116,11 +111,6 @@ public static LocalValue ConvertFrom(bool? value) return new NullLocalValue(); } - public static LocalValue ConvertFrom(int value) - { - return new NumberLocalValue(value); - } - public static LocalValue ConvertFrom(int? value) { if (value is int b) @@ -131,11 +121,6 @@ public static LocalValue ConvertFrom(int? value) return new NullLocalValue(); } - public static LocalValue ConvertFrom(double value) - { - return new NumberLocalValue(value); - } - public static LocalValue ConvertFrom(double? value) { if (value is double b) @@ -146,11 +131,6 @@ public static LocalValue ConvertFrom(double? value) return new NullLocalValue(); } - public static LocalValue ConvertFrom(long value) - { - return new NumberLocalValue(value); - } - public static LocalValue ConvertFrom(long? value) { if (value is long b) @@ -171,52 +151,63 @@ public static LocalValue ConvertFrom(string? value) return new NullLocalValue(); } - public static LocalValue ConvertFrom(DateTime dateTime) + public static LocalValue ConvertFrom(DateTime? value) { - return new DateLocalValue(dateTime.ToString("o")); + if (value is null) + { + return new NullLocalValue(); + } + + return new DateLocalValue(value.Value.ToString("o")); } - public static LocalValue ConvertFrom(BigInteger bigInt) + public static LocalValue ConvertFrom(BigInteger? value) { - return new BigIntLocalValue(bigInt.ToString()); + if (value is not null) + { + return new BigIntLocalValue(value.Value.ToString()); + } + + return new NullLocalValue(); } - public static LocalValue ConvertFrom(IEnumerable? values) + public static LocalValue ConvertFrom(IEnumerable? value) { - if (values is null) + if (value is null) { return new NullLocalValue(); } - LocalValue[] convertedList = values.Select(ConvertFrom).ToArray(); + LocalValue[] convertedList = [.. value.Select(ConvertFrom)]; return new ArrayLocalValue(convertedList); } - public static LocalValue ConvertFrom(IList? values) + public static LocalValue ConvertFrom(IList? value) { - if (values is null) + if (value is null) { return new NullLocalValue(); } - List convertedList = [.. values.Select(element => ConvertFrom(element))]; + List convertedList = [.. value.Select(element => ConvertFrom(element))]; return new ArrayLocalValue(convertedList); } - public static LocalValue ConvertFrom(IDictionary? dictionary) + public static LocalValue ConvertFrom(IDictionary? value) { - return ConvertFrom(dictionary); + return ConvertFrom(value); } - public static LocalValue ConvertFrom(IDictionary? dictionary) + public static LocalValue ConvertFrom(IDictionary? value) { - if (dictionary is null) + if (value is null) { return new NullLocalValue(); } - var bidiObject = new List>(dictionary.Count); - foreach (KeyValuePair item in dictionary) + var bidiObject = new List>(value.Count); + + foreach (KeyValuePair item in value) { bidiObject.Add([ConvertFrom(item.Key), ConvertFrom(item.Value)]); } @@ -229,14 +220,15 @@ public static LocalValue ConvertFrom(IDictionary? d return new MapLocalValue(bidiObject); } - public static LocalValue ConvertFrom(ISet? set) + public static LocalValue ConvertFrom(ISet? value) { - if (set is null) + if (value is null) { return new NullLocalValue(); } - LocalValue[] convertedValues = [.. set.Select(x => ConvertFrom(x))]; + LocalValue[] convertedValues = [.. value.Select(x => ConvertFrom(x))]; + return new SetLocalValue(convertedValues); } @@ -252,9 +244,11 @@ private static LocalValue ReflectionBasedConvertFrom(object? value) System.Reflection.PropertyInfo[] properties = value.GetType().GetProperties(Flags); var values = new List>(properties.Length); + foreach (System.Reflection.PropertyInfo? property in properties) { object? propertyValue; + try { propertyValue = property.GetValue(value); @@ -263,6 +257,7 @@ private static LocalValue ReflectionBasedConvertFrom(object? value) { throw new BiDiException($"Could not retrieve property {property.Name} from {property.DeclaringType}", ex); } + values.Add([property.Name, ConvertFrom(propertyValue)]); } diff --git a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs index c0a1058a55ef5..c1d26c1849609 100644 --- a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs +++ b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs @@ -19,6 +19,8 @@ using NUnit.Framework; using OpenQA.Selenium.BiDi.Modules.Script; +using System; +using System.Collections.Generic; namespace OpenQA.Selenium.BiDi.Script; @@ -28,72 +30,151 @@ class LocalValueConversionTests public void CanConvertNullBoolToLocalValue() { bool? arg = null; - LocalValue result = arg; - Assert.That(result, Is.TypeOf()); + + AssertValue(arg); + AssertValue(LocalValue.ConvertFrom(arg)); + + static void AssertValue(LocalValue value) + { + Assert.That(value, Is.TypeOf()); + } } [Test] public void CanConvertTrueToLocalValue() { - LocalValue result = true; - Assert.That(result, Is.TypeOf()); - Assert.That((result as BooleanLocalValue).Value, Is.True); + AssertValue(true); + + AssertValue(LocalValue.ConvertFrom(true)); + + static void AssertValue(LocalValue value) + { + Assert.That(value, Is.TypeOf()); + Assert.That((value 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); + AssertValue(false); + + AssertValue(LocalValue.ConvertFrom(false)); + + static void AssertValue(LocalValue value) + { + Assert.That(value, Is.TypeOf()); + Assert.That((value as BooleanLocalValue).Value, Is.False); + } } [Test] public void CanConvertNullIntToLocalValue() { int? arg = null; - LocalValue result = arg; - Assert.That(result, Is.TypeOf()); + + AssertValue(arg); + + AssertValue(LocalValue.ConvertFrom(arg)); + + static void AssertValue(LocalValue value) + { + Assert.That(value, Is.TypeOf()); + } } [Test] public void CanConvertZeroIntToLocalValue() { - LocalValue result = 0; - Assert.That(result, Is.TypeOf()); - Assert.That((result as NumberLocalValue).Value, Is.Zero); + LocalValue arg = 0; + + AssertValue(arg); + + AssertValue(LocalValue.ConvertFrom(0)); + + static void AssertValue(LocalValue value) + { + Assert.That(value, Is.TypeOf()); + Assert.That((value as NumberLocalValue).Value, Is.Zero); + } } [Test] public void CanConvertNullDoubleToLocalValue() { double? arg = null; - LocalValue result = arg; - Assert.That(result, Is.TypeOf()); + + AssertValue(arg); + + AssertValue(LocalValue.ConvertFrom(arg)); + + static void AssertValue(LocalValue value) + { + Assert.That(value, 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); + + AssertValue(arg); + + AssertValue(LocalValue.ConvertFrom(0)); + + static void AssertValue(LocalValue value) + { + Assert.That(value, Is.TypeOf()); + Assert.That((value as NumberLocalValue).Value, Is.Zero); + } } [Test] public void CanConvertNullStringToLocalValue() { string arg = null; - LocalValue result = arg; - Assert.That(result, Is.TypeOf()); + + AssertValue(arg); + + AssertValue(LocalValue.ConvertFrom(arg)); + + static void AssertValue(LocalValue value) + { + Assert.That(value, Is.TypeOf()); + } } [Test] public void CanConvertStringToLocalValue() { - LocalValue result = "value"; - Assert.That(result, Is.TypeOf()); - Assert.That((result as StringLocalValue).Value, Is.EqualTo("value")); + AssertValue("value"); + + AssertValue(LocalValue.ConvertFrom("value")); + + static void AssertValue(LocalValue value) + { + Assert.That(value, Is.TypeOf()); + Assert.That((value as StringLocalValue).Value, Is.EqualTo("value")); + } + } + + [Test] + public void CanConvertObjectValue() + { + var my = new MyClass() { IntNumber = 5 }; + + var value = LocalValue.ConvertFrom(my); + + Console.WriteLine(value); + + Assert.That(value, Is.TypeOf()); + } + + class MyClass + { + public int IntNumber { get; set; } + + public List ListOfInt { get; set; } } } From 0f4e733e773c3c0c236221e5da68f77fbeeb790e Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sat, 12 Apr 2025 13:19:49 +0300 Subject: [PATCH 04/14] Use anonymous in test --- .../common/BiDi/Script/LocalValueConversionTests.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs index c1d26c1849609..79ef7237c94bc 100644 --- a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs +++ b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs @@ -162,19 +162,12 @@ static void AssertValue(LocalValue value) [Test] public void CanConvertObjectValue() { - var my = new MyClass() { IntNumber = 5 }; + var arg = new { UIntNumber = 5u }; - var value = LocalValue.ConvertFrom(my); + var value = LocalValue.ConvertFrom(arg); Console.WriteLine(value); Assert.That(value, Is.TypeOf()); } - - class MyClass - { - public int IntNumber { get; set; } - - public List ListOfInt { get; set; } - } } From 99f2a9c5cff7ad915987eaf26f6f86c0191e61d1 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sat, 12 Apr 2025 14:25:08 +0300 Subject: [PATCH 05/14] Non-generic IList --- .../webdriver/BiDi/Modules/Script/LocalValue.cs | 17 +++++++++++++---- .../BiDi/Script/LocalValueConversionTests.cs | 11 ++++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index b16d9e799f7e7..4642362443138 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -18,6 +18,7 @@ // using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -90,7 +91,7 @@ public static LocalValue ConvertFrom(object? value) case ISet set: return ConvertFrom(set); - case IList set: + case IList set: return ConvertFrom(set); case IEnumerable list: @@ -182,15 +183,23 @@ public static LocalValue ConvertFrom(IEnumerable? value) return new ArrayLocalValue(convertedList); } - public static LocalValue ConvertFrom(IList? value) + public static LocalValue ConvertFrom(IList? value) { if (value is null) { return new NullLocalValue(); } - List convertedList = [.. value.Select(element => ConvertFrom(element))]; - return new ArrayLocalValue(convertedList); + var type = value.GetType(); + + List list = []; + + foreach (var element in value) + { + list.Add(ConvertFrom(element)); + } + + return new ArrayLocalValue(list); } public static LocalValue ConvertFrom(IDictionary? value) diff --git a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs index 79ef7237c94bc..61c796458cc95 100644 --- a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs +++ b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs @@ -162,12 +162,21 @@ static void AssertValue(LocalValue value) [Test] public void CanConvertObjectValue() { - var arg = new { UIntNumber = 5u }; + var arg = new + { + UIntNumber = 5u, + Array = new int[] { 1, 2 }, + List = new List { "a", "b" } + }; var value = LocalValue.ConvertFrom(arg); Console.WriteLine(value); Assert.That(value, Is.TypeOf()); + + var objValue = value as ObjectLocalValue; + + Assert.That(objValue.Value, Has.Exactly(3).Count); } } From 013be9be37a87e95d9b3bc494176442b86f7982d Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sat, 12 Apr 2025 14:25:27 +0300 Subject: [PATCH 06/14] Update LocalValue.cs --- dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index 4642362443138..c3dbc91088bdb 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -190,8 +190,6 @@ public static LocalValue ConvertFrom(IList? value) return new NullLocalValue(); } - var type = value.GetType(); - List list = []; foreach (var element in value) From ef8c5dba500eaad4af2649d681ed72569576ef62 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sat, 12 Apr 2025 14:29:50 +0300 Subject: [PATCH 07/14] Non-generic IEnumerable --- .../BiDi/Modules/Script/LocalValue.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index c3dbc91088bdb..62c9022e9e3a7 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -94,8 +94,8 @@ public static LocalValue ConvertFrom(object? value) case IList set: return ConvertFrom(set); - case IEnumerable list: - return ConvertFrom(list); + case IEnumerable enumerable: + return ConvertFrom(enumerable); default: return ReflectionBasedConvertFrom(value); @@ -172,18 +172,24 @@ public static LocalValue ConvertFrom(BigInteger? value) return new NullLocalValue(); } - public static LocalValue ConvertFrom(IEnumerable? value) + public static LocalValue ConvertFrom(IList? value) { if (value is null) { return new NullLocalValue(); } - LocalValue[] convertedList = [.. value.Select(ConvertFrom)]; - return new ArrayLocalValue(convertedList); + List list = []; + + foreach (var element in value) + { + list.Add(ConvertFrom(element)); + } + + return new ArrayLocalValue(list); } - public static LocalValue ConvertFrom(IList? value) + public static LocalValue ConvertFrom(IEnumerable? value) { if (value is null) { From 8e17566ffdf362a64b1c96fc4f73ce15a8ab1d00 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sat, 12 Apr 2025 14:37:54 +0300 Subject: [PATCH 08/14] Non-generic Dictionary --- .../BiDi/Modules/Script/LocalValue.cs | 24 ++++--------------- .../BiDi/Script/LocalValueConversionTests.cs | 5 ++-- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index 62c9022e9e3a7..da9a592eeb8f5 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -79,13 +79,7 @@ public static LocalValue ConvertFrom(object? value) case string str: return ConvertFrom(str); - case IDictionary dictionary: - return ConvertFrom(dictionary); - - case IDictionary dictionary: - return ConvertFrom(dictionary); - - case IDictionary dictionary: + case IDictionary dictionary: return ConvertFrom(dictionary); case ISet set: @@ -206,12 +200,7 @@ public static LocalValue ConvertFrom(IEnumerable? value) return new ArrayLocalValue(list); } - public static LocalValue ConvertFrom(IDictionary? value) - { - return ConvertFrom(value); - } - - public static LocalValue ConvertFrom(IDictionary? value) + public static LocalValue ConvertFrom(IDictionary? value) { if (value is null) { @@ -220,14 +209,9 @@ public static LocalValue ConvertFrom(IDictionary? v var bidiObject = new List>(value.Count); - foreach (KeyValuePair item in value) - { - bidiObject.Add([ConvertFrom(item.Key), ConvertFrom(item.Value)]); - } - - if (typeof(TKey) == typeof(string)) + foreach (var key in value.Keys) { - return new ObjectLocalValue(bidiObject); + bidiObject.Add([ConvertFrom(key), ConvertFrom(value[key])]); } return new MapLocalValue(bidiObject); diff --git a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs index 61c796458cc95..ecda10c382394 100644 --- a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs +++ b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs @@ -166,7 +166,8 @@ public void CanConvertObjectValue() { UIntNumber = 5u, Array = new int[] { 1, 2 }, - List = new List { "a", "b" } + List = new List { "a", "b" }, + Dictionary = new Dictionary { { "a", 1 }, { "b", 2 } } }; var value = LocalValue.ConvertFrom(arg); @@ -177,6 +178,6 @@ public void CanConvertObjectValue() var objValue = value as ObjectLocalValue; - Assert.That(objValue.Value, Has.Exactly(3).Count); + Assert.That(objValue.Value, Has.Exactly(4).Count); } } From 78e068b20074a41df5287254fbcfd3dba04219ac Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sat, 12 Apr 2025 15:47:45 +0300 Subject: [PATCH 09/14] Support Set --- .../BiDi/Modules/Script/LocalValue.cs | 20 ++++++++++++++----- .../BiDi/Script/LocalValueConversionTests.cs | 5 +++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index da9a592eeb8f5..9f0380cae0872 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -79,14 +79,24 @@ public static LocalValue ConvertFrom(object? value) case string str: return ConvertFrom(str); + case { } when value.GetType().GetInterfaces() + .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ISet<>)): + IEnumerable set = (IEnumerable)value; + + List setValues = []; + + foreach (var obj in set) + { + setValues.Add(ConvertFrom(obj)); + } + + return new SetLocalValue(setValues); + case IDictionary dictionary: return ConvertFrom(dictionary); - case ISet set: - return ConvertFrom(set); - - case IList set: - return ConvertFrom(set); + case IList list: + return ConvertFrom(list); case IEnumerable enumerable: return ConvertFrom(enumerable); diff --git a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs index ecda10c382394..cbf4b68b820e0 100644 --- a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs +++ b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs @@ -167,7 +167,8 @@ public void CanConvertObjectValue() UIntNumber = 5u, Array = new int[] { 1, 2 }, List = new List { "a", "b" }, - Dictionary = new Dictionary { { "a", 1 }, { "b", 2 } } + Dictionary = new Dictionary { { "a", 1 }, { "b", 2 } }, + Set = new HashSet { "a", "b" } }; var value = LocalValue.ConvertFrom(arg); @@ -178,6 +179,6 @@ public void CanConvertObjectValue() var objValue = value as ObjectLocalValue; - Assert.That(objValue.Value, Has.Exactly(4).Count); + Assert.That(objValue.Value, Has.Exactly(5).Count); } } From 0f8b53d750a5177872e6205e2c6f724b73eea355 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sat, 12 Apr 2025 15:57:50 +0300 Subject: [PATCH 10/14] IList and IEnumerable are ArrayLocalValue --- .../BiDi/Modules/Script/LocalValue.cs | 36 +++++-------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index 9f0380cae0872..10bc15d93f39d 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -81,23 +81,22 @@ public static LocalValue ConvertFrom(object? value) case { } when value.GetType().GetInterfaces() .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ISet<>)): - IEnumerable set = (IEnumerable)value; + { + IEnumerable set = (IEnumerable)value; - List setValues = []; + List setValues = []; - foreach (var obj in set) - { - setValues.Add(ConvertFrom(obj)); - } + foreach (var obj in set) + { + setValues.Add(ConvertFrom(obj)); + } - return new SetLocalValue(setValues); + return new SetLocalValue(setValues); + } case IDictionary dictionary: return ConvertFrom(dictionary); - case IList list: - return ConvertFrom(list); - case IEnumerable enumerable: return ConvertFrom(enumerable); @@ -176,23 +175,6 @@ public static LocalValue ConvertFrom(BigInteger? value) return new NullLocalValue(); } - public static LocalValue ConvertFrom(IList? value) - { - if (value is null) - { - return new NullLocalValue(); - } - - List list = []; - - foreach (var element in value) - { - list.Add(ConvertFrom(element)); - } - - return new ArrayLocalValue(list); - } - public static LocalValue ConvertFrom(IEnumerable? value) { if (value is null) From 7990878959f74f535520b8123fc3998a391b7877 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:50:59 +0300 Subject: [PATCH 11/14] Add minimal tests for collections --- .../BiDi/Script/LocalValueConversionTests.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs index cbf4b68b820e0..c7b147f3da001 100644 --- a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs +++ b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs @@ -21,6 +21,8 @@ using OpenQA.Selenium.BiDi.Modules.Script; using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; namespace OpenQA.Selenium.BiDi.Script; @@ -159,6 +161,46 @@ static void AssertValue(LocalValue value) } } + [Test] + public void CanConvertArrayToLocalValue() + { + AssertValue(LocalValue.ConvertFrom(new List { 1, 2 })); + + AssertValue(LocalValue.ConvertFrom(new string[] { "a", "b" })); + + static void AssertValue(LocalValue value) + { + Assert.That(value, Is.TypeOf()); + Assert.That((value as ArrayLocalValue).Value.Count, Is.EqualTo(2)); + } + } + + [Test] + public void CanConvertMapToLocalValue() + { + AssertValue(LocalValue.ConvertFrom(new Dictionary { { 1, "a" }, { 2, "b" } })); + + static void AssertValue(LocalValue value) + { + Assert.That(value, Is.TypeOf()); + Assert.That((value as MapLocalValue).Value.Count, Is.EqualTo(2)); + } + } + + [Test] + public void CanConvertSetToLocalValue() + { + AssertValue(LocalValue.ConvertFrom(new HashSet { 1, 2 })); + + AssertValue(LocalValue.ConvertFrom(ImmutableHashSet.CreateRange([1, 2]))); + + static void AssertValue(LocalValue value) + { + Assert.That(value, Is.TypeOf()); + Assert.That((value as SetLocalValue).Value.Count, Is.EqualTo(2)); + } + } + [Test] public void CanConvertObjectValue() { From efe6a574a781568f0e86fe494d32acdcea506b07 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Sun, 13 Apr 2025 00:26:03 -0400 Subject: [PATCH 12/14] Add ConvertFrom(Regex) support --- .../BiDi/Modules/Script/LocalValue.cs | 25 +++++++++ .../BiDi/Modules/Script/RegExpValue.cs | 53 +++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index 10bc15d93f39d..dad95cb4b1fb6 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -23,6 +23,7 @@ using System.Linq; using System.Numerics; using System.Text.Json.Serialization; +using System.Text.RegularExpressions; namespace OpenQA.Selenium.BiDi.Modules.Script; @@ -79,6 +80,9 @@ public static LocalValue ConvertFrom(object? value) case string str: return ConvertFrom(str); + case Regex regex: + return ConvertFrom(regex); + case { } when value.GetType().GetInterfaces() .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ISet<>)): { @@ -155,6 +159,27 @@ public static LocalValue ConvertFrom(string? value) return new NullLocalValue(); } + /// + /// 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 LocalValue ConvertFrom(Regex? regex) + { + if (regex is null) + { + return new NullLocalValue(); + } + + string? flags = RegExpValue.GetRegExpFlags(regex.Options); + + return new RegExpLocalValue(new RegExpValue(regex.ToString()) { Flags = flags }); + } + public static LocalValue ConvertFrom(DateTime? value) { if (value is null) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/RegExpValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/RegExpValue.cs index 47a2239ab093d..eb25c091e8f91 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/RegExpValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/RegExpValue.cs @@ -17,9 +17,62 @@ // under the License. // +using System; +using System.Diagnostics; +using System.Text.RegularExpressions; + namespace OpenQA.Selenium.BiDi.Modules.Script; public record RegExpValue(string Pattern) { public string? Flags { get; set; } + + internal static string? GetRegExpFlags(RegexOptions options) + { + if (options == RegexOptions.None) + { + return null; + } + + 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 flags; + } } From 352ca9909a523f9c6daa32ccf4cecc44cfd82e16 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Sun, 13 Apr 2025 00:57:13 -0400 Subject: [PATCH 13/14] Use `arg` in tests --- dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs index c7b147f3da001..33e4a8637fb90 100644 --- a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs +++ b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs @@ -88,11 +88,11 @@ static void AssertValue(LocalValue value) [Test] public void CanConvertZeroIntToLocalValue() { - LocalValue arg = 0; + int arg = 0; AssertValue(arg); - AssertValue(LocalValue.ConvertFrom(0)); + AssertValue(LocalValue.ConvertFrom(arg)); static void AssertValue(LocalValue value) { @@ -123,7 +123,7 @@ public void CanConvertZeroDoubleToLocalValue() AssertValue(arg); - AssertValue(LocalValue.ConvertFrom(0)); + AssertValue(LocalValue.ConvertFrom(arg)); static void AssertValue(LocalValue value) { From ab59bdf62e8463ad7a496a0b2b86892c1f2a55ee Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sun, 13 Apr 2025 17:54:58 +0300 Subject: [PATCH 14/14] Use DateTimeOffset for now --- .../webdriver/BiDi/Modules/Script/LocalValue.cs | 5 +++-- .../BiDi/Script/LocalValueConversionTests.cs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs index dad95cb4b1fb6..2e458c0377920 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -47,6 +47,7 @@ public abstract record LocalValue public static implicit operator LocalValue(int? value) { return ConvertFrom(value); } public static implicit operator LocalValue(double? value) { return ConvertFrom(value); } public static implicit operator LocalValue(string? value) { return ConvertFrom(value); } + public static implicit operator LocalValue(DateTimeOffset? value) { return ConvertFrom(value); } // TODO: Extend converting from types public static LocalValue ConvertFrom(object? value) @@ -71,7 +72,7 @@ public static LocalValue ConvertFrom(object? value) case long l: return ConvertFrom(l); - case DateTime dt: + case DateTimeOffset dt: return ConvertFrom(dt); case BigInteger bigInt: @@ -180,7 +181,7 @@ public static LocalValue ConvertFrom(Regex? regex) return new RegExpLocalValue(new RegExpValue(regex.ToString()) { Flags = flags }); } - public static LocalValue ConvertFrom(DateTime? value) + public static LocalValue ConvertFrom(DateTimeOffset? value) { if (value is null) { diff --git a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs index 33e4a8637fb90..0ef090b7a0bcd 100644 --- a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs +++ b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs @@ -161,6 +161,22 @@ static void AssertValue(LocalValue value) } } + [Test] + public void CanConvertDateTimeOffsetToLocalValue() + { + var date = new DateTimeOffset(2025, 4, 13, 5, 40, 20, 123, 456, TimeSpan.FromHours(+3)); + + AssertValue(date); + + AssertValue(LocalValue.ConvertFrom(date)); + + static void AssertValue(LocalValue value) + { + Assert.That(value, Is.TypeOf()); + Assert.That((value as DateLocalValue).Value, Is.EqualTo("2025-04-13T05:40:20.1234560+03:00")); + } + } + [Test] public void CanConvertArrayToLocalValue() {