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 cbb70c50a6b20..19be05efaaf18 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -17,7 +17,10 @@ // under the License. // +using System; using System.Collections.Generic; +using System.Linq; +using System.Numerics; using System.Text.Json.Serialization; namespace OpenQA.Selenium.BiDi.Modules.Script; @@ -38,7 +41,9 @@ 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(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 @@ -46,25 +51,88 @@ 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; - case object: + + case bool b: + return new BooleanLocalValue(b); + + case int i: + return new NumberLocalValue(i); + + 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()); + + case string str: + return new StringLocalValue(str); + + case IDictionary dictionary: { - var type = value.GetType(); + var bidiObject = new List>(dictionary.Count); + foreach (var item in dictionary) + { + bidiObject.Add([new StringLocalValue(item.Key), ConvertFrom(item.Value)]); + } - var properties = type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); + return new ObjectLocalValue(bidiObject); + } + + 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); + } + + 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); + } + + 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; - List> values = []; + var properties = value.GetType().GetProperties(Flags); + var values = new List>(properties.Length); foreach (var property in properties) { - values.Add([property.Name, ConvertFrom(property.GetValue(value))]); + 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); diff --git a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs index 5def3928912e6..68fb29a1a1145 100644 --- a/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs +++ b/dotnet/test/common/BiDi/Script/CallFunctionLocalValueTest.cs @@ -57,7 +57,7 @@ await context.Script.CallFunctionAsync($$""" } [Test] - public void CanCallFunctionWithArgumentBoolean() + public void CanCallFunctionWithArgumentTrue() { var arg = new BooleanLocalValue(true); Assert.That(async () => @@ -72,6 +72,22 @@ await context.Script.CallFunctionAsync($$""" }, Throws.Nothing); } + [Test] + public void CanCallFunctionWithArgumentFalse() + { + var arg = new BooleanLocalValue(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() { diff --git a/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs new file mode 100644 index 0000000000000..c0a1058a55ef5 --- /dev/null +++ b/dotnet/test/common/BiDi/Script/LocalValueConversionTests.cs @@ -0,0 +1,99 @@ +// +// 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() + { + 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")); + } +}