Skip to content

[dotnet] [bidi] Convert RemoteValue to well-known types#17027

Merged
nvborisenko merged 10 commits intoSeleniumHQ:trunkfrom
nvborisenko:bidi-convert-remote-value
Mar 3, 2026
Merged

[dotnet] [bidi] Convert RemoteValue to well-known types#17027
nvborisenko merged 10 commits intoSeleniumHQ:trunkfrom
nvborisenko:bidi-convert-remote-value

Conversation

@nvborisenko
Copy link
Copy Markdown
Member

🔗 Related Issues

Contribues to #16719

💥 What does this PR do?

🔧 Implementation Notes

💡 Additional Considerations

🔄 Types of changes

  • Cleanup (formatting, renaming)
  • Bug fix (backwards compatible)
  • New feature (non-breaking change which adds functionality and tests!)
  • Breaking change (fix or feature that would cause existing functionality to change)

@nvborisenko nvborisenko marked this pull request as draft January 30, 2026 18:03
@selenium-ci selenium-ci added the C-dotnet .NET Bindings label Jan 30, 2026
@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review bot commented Jan 30, 2026

Review Summary by Qodo

Enhance RemoteValue type conversion with comprehensive support

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Refactored RemoteValue.ConvertTo<T>() to support comprehensive type conversions
• Added implicit operators for bool, int, long, string conversions
• Implemented array and generic list conversion support with reflection
• Added comprehensive test suite covering all conversion scenarios

Grey Divider

File Changes

1. dotnet/src/webdriver/BiDi/Script/RemoteValue.cs ✨ Enhancement +77/-31

Refactor RemoteValue type conversion system

• Replaced individual conversion methods with unified ConvertTo<TResult>() using pattern matching
• Added support for multiple numeric types (short, ushort, int, uint, long, ulong, float, double)
• Implemented array and generic list conversion using reflection
• Added implicit operators for bool, int, long, string types
• Improved error handling with BiDiException

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs


2. dotnet/test/common/BiDi/Script/RemoteValueConversionTest.cs 🧪 Tests +248/-0

Add comprehensive RemoteValue conversion tests

• Created comprehensive test suite with 15 test methods
• Tests cover conversions to nullable types, primitives, and collections
• Validates both explicit ConvertTo<T>() calls and implicit operator conversions
• Tests array and generic list conversions with empty and populated collections

dotnet/test/common/BiDi/Script/RemoteValueConversionTest.cs


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review bot commented Jan 30, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Denial of service

Description: Converting ArrayRemoteValue to arrays/lists performs unbounded allocations based on
remoteValues size (ToList(), Array.CreateInstance, and list growth), which could allow a
remote endpoint to trigger excessive memory usage (DoS) by returning extremely large
arrays.
RemoteValue.cs [112-149]

Referred Code
private static TResult ConvertRemoteValuesToArray<TResult>(IEnumerable<RemoteValue>? remoteValues, Type elementType)
{
    if (remoteValues is null)
    {
        return (TResult)(object)Array.CreateInstance(elementType, 0);
    }

    var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);
    var items = remoteValues.ToList();
    var array = Array.CreateInstance(elementType, items.Count);

    for (int i = 0; i < items.Count; i++)
    {
        var convertedItem = convertMethod.Invoke(items[i], null);
        array.SetValue(convertedItem, i);
    }

    return (TResult)(object)array;
}

private static TResult ConvertRemoteValuesToGenericList<TResult>(IEnumerable<RemoteValue>? remoteValues, Type listType)


 ... (clipped 17 lines)
Ticket Compliance
🟡
🎫 #16719
🟢 Add implicit conversion support from Script.RemoteValue to .NET well-known types to enable
patterns like await bidi.Script.EvaluateAsync(...).
Extend the set of supported .NET types for converting RemoteValue (beyond the few
primitive examples already present).
Confirm EvaluateAsync (and any other BiDi Script APIs returning RemoteValue) actually uses
these implicit operators / ConvertTo() end-to-end in real BiDi sessions.
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing edge handling: Numeric and reflective conversions can throw runtime exceptions (e.g., overflow or
reflection Invoke failures) without being caught/wrapped to provide actionable context or
graceful degradation.

Referred Code
(NumberRemoteValue n, Type t) when t == typeof(short)
    => (TResult)(object)Convert.ToInt16(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(ushort)
    => (TResult)(object)Convert.ToUInt16(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(int)
    => (TResult)(object)Convert.ToInt32(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(uint)
    => (TResult)(object)Convert.ToUInt32(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(long)
    => (TResult)(object)Convert.ToInt64(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(ulong)
    => (TResult)(object)Convert.ToUInt64(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(double)
    => (TResult)(object)n.Value,
(NumberRemoteValue n, Type t) when t == typeof(float)
    => (TResult)(object)Convert.ToSingle(n.Value),
(StringRemoteValue s, Type t) when t == typeof(string)
    => (TResult)(object)s.Value,
(ArrayRemoteValue a, Type t) when t.IsArray
    => ConvertRemoteValuesToArray<TResult>(a.Value, t.GetElementType()!),
(ArrayRemoteValue a, Type t) when t.IsGenericType && t.IsAssignableFrom(typeof(List<>).MakeGenericType(t.GetGenericArguments()[0]))


 ... (clipped 40 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Unvalidated value ranges: Converting externally-sourced numeric RemoteValue data via Convert.ToInt16/ToUInt64/...
lacks explicit range/NaN/Infinity validation and may throw or behave unexpectedly
depending on upstream inputs.

Referred Code
(NumberRemoteValue n, Type t) when t == typeof(short)
    => (TResult)(object)Convert.ToInt16(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(ushort)
    => (TResult)(object)Convert.ToUInt16(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(int)
    => (TResult)(object)Convert.ToInt32(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(uint)
    => (TResult)(object)Convert.ToUInt32(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(long)
    => (TResult)(object)Convert.ToInt64(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(ulong)
    => (TResult)(object)Convert.ToUInt64(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(double)
    => (TResult)(object)n.Value,
(NumberRemoteValue n, Type t) when t == typeof(float)
    => (TResult)(object)Convert.ToSingle(n.Value),

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@nvborisenko
Copy link
Copy Markdown
Member Author

@RenderMichael please quickly take a look whether it is good direction.

@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review bot commented Jan 30, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Reflection-based conversion creates performance bottleneck

The current implementation uses reflection for converting elements within arrays
and lists, which can be slow. To improve performance, consider caching the
reflected methods to avoid repeated lookups.

Examples:

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs [112-130]
    private static TResult ConvertRemoteValuesToArray<TResult>(IEnumerable<RemoteValue>? remoteValues, Type elementType)
    {
        if (remoteValues is null)
        {
            return (TResult)(object)Array.CreateInstance(elementType, 0);
        }

        var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);
        var items = remoteValues.ToList();
        var array = Array.CreateInstance(elementType, items.Count);

 ... (clipped 9 lines)
dotnet/src/webdriver/BiDi/Script/RemoteValue.cs [132-148]
    private static TResult ConvertRemoteValuesToGenericList<TResult>(IEnumerable<RemoteValue>? remoteValues, Type listType)
    {
        var elementType = listType.GetGenericArguments()[0];
        var list = (System.Collections.IList)Activator.CreateInstance(listType)!;

        if (remoteValues is not null)
        {
            var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);

            foreach (var item in remoteValues)

 ... (clipped 7 lines)

Solution Walkthrough:

Before:

private static TResult ConvertRemoteValuesToArray<TResult>(IEnumerable<RemoteValue>? remoteValues, Type elementType)
{
    if (remoteValues is null)
    {
        return (TResult)(object)Array.CreateInstance(elementType, 0);
    }

    var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);
    var items = remoteValues.ToList();
    var array = Array.CreateInstance(elementType, items.Count);

    for (int i = 0; i < items.Count; i++)
    {
        var convertedItem = convertMethod.Invoke(items[i], null);
        array.SetValue(convertedItem, i);
    }

    return (TResult)(object)array;
}

After:

private static readonly System.Collections.Concurrent.ConcurrentDictionary<Type, System.Reflection.MethodInfo> _converterCache = new();

private static TResult ConvertRemoteValuesToArray<TResult>(IEnumerable<RemoteValue>? remoteValues, Type elementType)
{
    if (remoteValues is null)
    {
        return (TResult)(object)Array.CreateInstance(elementType, 0);
    }

    var convertMethod = _converterCache.GetOrAdd(elementType, 
        type => typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(type));
    
    var items = remoteValues.ToList();
    var array = Array.CreateInstance(elementType, items.Count);

    for (int i = 0; i < items.Count; i++)
    {
        var convertedItem = convertMethod.Invoke(items[i], null);
        array.SetValue(convertedItem, i);
    }

    return (TResult)(object)array;
}
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a significant performance issue due to reflection in collection conversions, which is a valid and important concern for a library feature.

Medium
General
Generalize null conversion for all types
Suggestion Impact:Replaced the explicit list of allowed null-convertible types with a generic condition (!t.IsValueType || Nullable.GetUnderlyingType(t) != null) and added additional nullable conversion handling.

code diff:

-            (NullRemoteValue _, Type t) when
-                t == typeof(bool?) ||
-                t == typeof(short?) ||
-                t == typeof(ushort?) ||
-                t == typeof(int?) ||
-                t == typeof(uint?) ||
-                t == typeof(long?) ||
-                t == typeof(ulong?) ||
-                t == typeof(double?) ||
-                t == typeof(float?) ||
-                t == typeof(string)
+            (NullRemoteValue, Type t) when !t.IsValueType || Nullable.GetUnderlyingType(t) is not null
                 => default,
             (NumberRemoteValue n, Type t) when t == typeof(short)
                 => (TResult)(object)Convert.ToInt16(n.Value),
@@ -106,8 +92,18 @@
             (ArrayRemoteValue a, Type t) when t.IsGenericType && t.IsAssignableFrom(typeof(List<>).MakeGenericType(t.GetGenericArguments()[0]))
                 => ConvertRemoteValuesToGenericList<TResult>(a.Value, typeof(List<>).MakeGenericType(t.GetGenericArguments()[0])),
 
-            _ => throw new BiDiException($"Cannot convert {GetType().Name} to {typeof(TResult).FullName}")
+            (_, Type t) when Nullable.GetUnderlyingType(t) is { } underlying
+                => ConvertToNullable<TResult>(underlying),
+
+            _ => throw new InvalidCastException($"Cannot convert {GetType().Name} to {typeof(TResult).FullName}")
         };
+
+    private TResult ConvertToNullable<TResult>(Type underlyingType)
+    {
+        var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(underlyingType);
+        var value = convertMethod.Invoke(this, null);
+        return (TResult)value!;
+    }
 

Generalize the NullRemoteValue conversion to support all nullable and reference
types by replacing the hardcoded list with a check for !t.IsValueType or if the
type is Nullable.

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs [74-85]

-(NullRemoteValue _, Type t) when
-    t == typeof(bool?) ||
-    t == typeof(short?) ||
-    t == typeof(ushort?) ||
-    t == typeof(int?) ||
-    t == typeof(uint?) ||
-    t == typeof(long?) ||
-    t == typeof(ulong?) ||
-    t == typeof(double?) ||
-    t == typeof(float?) ||
-    t == typeof(string)
+(NullRemoteValue _, Type t) when !t.IsValueType || Nullable.GetUnderlyingType(t) is not null
     => default,

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the hardcoded list of types for null conversion is brittle and replaces it with a more robust, generic check, improving maintainability.

Medium
Improve generic list conversion logic

Refine the ArrayRemoteValue conversion logic to distinguish between
interfaces/abstract classes and concrete list types, creating a List for the
former and an instance of the specific type for the latter.

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs [106-107]

-(ArrayRemoteValue a, Type t) when t.IsGenericType && t.IsAssignableFrom(typeof(List<>).MakeGenericType(t.GetGenericArguments()[0]))
+(ArrayRemoteValue a, Type t) when t.IsGenericType && (t.IsInterface || t.IsAbstract) && t.IsAssignableFrom(typeof(List<>).MakeGenericType(t.GetGenericArguments()[0]))
     => ConvertRemoteValuesToGenericList<TResult>(a.Value, typeof(List<>).MakeGenericType(t.GetGenericArguments()[0])),
+(ArrayRemoteValue a, Type t) when t.IsGenericType && !t.IsAbstract && typeof(System.Collections.IList).IsAssignableFrom(t)
+    => ConvertRemoteValuesToGenericList<TResult>(a.Value, t),
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: This is a good suggestion that refines the new collection conversion logic, making it more flexible by handling concrete list types differently from interfaces, which improves the feature's versatility.

Low
Use a smaller tolerance for float comparison

In the CanConvertToFloat test, reduce the assertion tolerance from 0.1f to a
much smaller value like 1e-6f to more accurately verify the precision of the
float conversion.

dotnet/test/common/BiDi/Script/RemoteValueConversionTest.cs [161-173]

 [Test]
 public void CanConvertToFloat()
 {
     NumberRemoteValue arg = new(5.1);
 
     AssertValue(arg.ConvertTo<float>());
     AssertValue((float)arg);
 
     static void AssertValue(float value)
     {
-        Assert.That(value, Is.EqualTo(5.1f).Within(0.1f));
+        Assert.That(value, Is.EqualTo(5.1f).Within(1e-6f));
     }
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out that the test tolerance for float conversion is too loose and proposes a stricter value, which improves the quality and reliability of the new test suite.

Low
Learned
best practice
Cache reflection-based conversions

Cache the constructed generic ConvertTo delegate per elementType (or avoid
reflection entirely when possible) to prevent repeated MethodInfo.Invoke calls
and reduce runtime overhead.

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs [112-130]

+private static readonly Dictionary<Type, Func<RemoteValue, object?>> ConvertToCache = new();
+
 private static TResult ConvertRemoteValuesToArray<TResult>(IEnumerable<RemoteValue>? remoteValues, Type elementType)
 {
     if (remoteValues is null)
     {
         return (TResult)(object)Array.CreateInstance(elementType, 0);
     }
 
-    var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);
+    if (!ConvertToCache.TryGetValue(elementType, out var converter))
+    {
+        var method = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);
+        converter = rv => method.Invoke(rv, null);
+        ConvertToCache[elementType] = converter;
+    }
+
     var items = remoteValues.ToList();
     var array = Array.CreateInstance(elementType, items.Count);
 
     for (int i = 0; i < items.Count; i++)
     {
-        var convertedItem = convertMethod.Invoke(items[i], null);
-        array.SetValue(convertedItem, i);
+        array.SetValue(converter(items[i]), i);
     }
 
     return (TResult)(object)array;
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Avoid per-item reflection (MethodInfo.Invoke) in hot-path conversions; prefer type-safe generic code or cached delegates for performance and reliability.

Low
Ensure tests are discoverable

Change the test class to public (and consider naming it
RemoteValueConversionTests) so NUnit test discovery is robust across tooling and
configurations.

dotnet/test/common/BiDi/Script/RemoteValueConversionTest.cs [25-248]

-internal class RemoteValueConversionTest
+public class RemoteValueConversionTests
 {
     [Test]
     public void CanConvertToNullable()
     {
         NullRemoteValue arg = new();
 
         AssertValue(arg.ConvertTo<bool?>());
         AssertValue(arg.ConvertTo<short?>());
         AssertValue(arg.ConvertTo<ushort?>());
         AssertValue(arg.ConvertTo<int?>());
         AssertValue(arg.ConvertTo<uint?>());
         AssertValue(arg.ConvertTo<long?>());
         AssertValue(arg.ConvertTo<ulong?>());
         AssertValue(arg.ConvertTo<double?>());
         AssertValue(arg.ConvertTo<float?>());
         AssertValue(arg.ConvertTo<string>());
 
         static void AssertValue<T>(T value)
         {
             Assert.That(value, Is.Null);
         }
     }
     ...
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Make test fixtures publicly discoverable and follow conventional naming to ensure consistent test discovery across runners.

Low
  • Update

Copilot AI review requested due to automatic review settings March 3, 2026 19:39
@nvborisenko nvborisenko marked this pull request as ready for review March 3, 2026 19:41
@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review bot commented Mar 3, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Invoke wraps BiDiException 🐞 Bug ⛯ Reliability
Description
Array/List conversion uses MethodInfo.Invoke; element conversion failures will surface as
TargetInvocationException instead of BiDiException, breaking callers’ exception handling and
obscuring the real error.
Code

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[R116-142]

+        var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);
+        var items = remoteValues.ToList();
+        var array = Array.CreateInstance(elementType, items.Count);
+
+        for (int i = 0; i < items.Count; i++)
  {
-            return (TResult)(((StringRemoteValue)this).Value as object);
+            var convertedItem = convertMethod.Invoke(items[i], null);
+            array.SetValue(convertedItem, i);
  }
-        else if (type is object)
+
+        return (TResult)(object)array;
+    }
+
+    private static TResult ConvertRemoteValuesToGenericList<TResult>(IEnumerable<RemoteValue>? remoteValues, Type listType)
+    {
+        var elementType = listType.GetGenericArguments()[0];
+        var list = (System.Collections.IList)Activator.CreateInstance(listType)!;
+
+        if (remoteValues is not null)
  {
-            // :)
-            return (TResult)new object();
+            var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);
+
+            foreach (var item in remoteValues)
+            {
+                var convertedItem = convertMethod.Invoke(item, null);
+                list.Add(convertedItem);
+            }
Evidence
Both array and list conversions use reflection Invoke without unwrapping TargetInvocationException,
so exceptions thrown by ConvertTo<T> will be wrapped and change the exception contract seen by
callers.

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[116-124]
dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[136-142]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ConvertRemoteValuesToArray` / `ConvertRemoteValuesToGenericList` call `ConvertTo&amp;amp;amp;amp;amp;amp;lt;T&amp;amp;amp;amp;amp;amp;gt;()` via `MethodInfo.Invoke()`. When `ConvertTo&amp;amp;amp;amp;amp;amp;lt;T&amp;amp;amp;amp;amp;amp;gt;()` throws (e.g., `BiDiException`), reflection wraps it in `TargetInvocationException`, changing the exception surface and making it harder for callers to catch/diagnose.
### Issue Context
These helpers are used when converting `ArrayRemoteValue` to `T[]` or to generic collection interfaces like `IEnumerable&amp;amp;amp;amp;amp;amp;lt;T&amp;amp;amp;amp;amp;amp;gt;`.
### Fix Focus Areas
- dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[109-146]
### Proposed fix sketch
- Wrap each `Invoke(...)` in `try/catch (TargetInvocationException ex) when (ex.InnerException != null)`
- Rethrow `ex.InnerException` (ideally with `ExceptionDispatchInfo.Capture(ex.InnerException).Throw();` to preserve stack)
- Optionally also wrap `array.SetValue(...)` / `list.Add(...)` to add contextual error messages (index, target type).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. ConvertTo throws BiDiException📘 Rule violation ✓ Correctness
Description
Unsupported conversions now throw BiDiException, which is a user-visible behavioral contract that
may break callers expecting standard cast exceptions (e.g., InvalidCastException). Consider
preserving prior exception semantics or explicitly documenting/deprecating the change.
Code

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[R57-107]

+    public static implicit operator bool(RemoteValue remoteValue) => remoteValue.ConvertTo<bool>();
+    public static implicit operator double(RemoteValue remoteValue) => remoteValue.ConvertTo<double>();
+    public static implicit operator int(RemoteValue remoteValue) => remoteValue.ConvertTo<int>();
+    public static implicit operator long(RemoteValue remoteValue) => remoteValue.ConvertTo<long>();
+    public static implicit operator string?(RemoteValue remoteValue) => remoteValue.ConvertTo<string>();
-    public static implicit operator int(RemoteValue remoteValue) => (int)(double)remoteValue;
-    public static implicit operator long(RemoteValue remoteValue) => (long)(double)remoteValue;
-
-    public static implicit operator string?(RemoteValue remoteValue)
-    {
-        return remoteValue switch
+    // TODO: extend types
+    public TResult? ConvertTo<TResult>()
+        => (this, typeof(TResult)) switch
  {
-            StringRemoteValue stringValue => stringValue.Value,
-            NullRemoteValue => null,
-            _ => throw new InvalidCastException($"Cannot convert {remoteValue} to string")
+            (_, Type t) when typeof(RemoteValue).IsAssignableFrom(t)
+                => (TResult)(object)this,
+            (BooleanRemoteValue b, Type t) when t == typeof(bool)
+                => (TResult)(object)b.Value,
+            (NullRemoteValue _, Type t) when
+                t == typeof(bool?) ||
+                t == typeof(short?) ||
+                t == typeof(ushort?) ||
+                t == typeof(int?) ||
+                t == typeof(uint?) ||
+                t == typeof(long?) ||
+                t == typeof(ulong?) ||
+                t == typeof(double?) ||
+                t == typeof(float?) ||
+                t == typeof(string)
+                => default,
+            (NumberRemoteValue n, Type t) when t == typeof(short)
+                => (TResult)(object)Convert.ToInt16(n.Value),
+            (NumberRemoteValue n, Type t) when t == typeof(ushort)
+                => (TResult)(object)Convert.ToUInt16(n.Value),
+            (NumberRemoteValue n, Type t) when t == typeof(int)
+                => (TResult)(object)Convert.ToInt32(n.Value),
+            (NumberRemoteValue n, Type t) when t == typeof(uint)
+                => (TResult)(object)Convert.ToUInt32(n.Value),
+            (NumberRemoteValue n, Type t) when t == typeof(long)
+                => (TResult)(object)Convert.ToInt64(n.Value),
+            (NumberRemoteValue n, Type t) when t == typeof(ulong)
+                => (TResult)(object)Convert.ToUInt64(n.Value),
+            (NumberRemoteValue n, Type t) when t == typeof(double)
+                => (TResult)(object)n.Value,
+            (NumberRemoteValue n, Type t) when t == typeof(float)
+                => (TResult)(object)Convert.ToSingle(n.Value),
+            (StringRemoteValue s, Type t) when t == typeof(string)
+                => (TResult)(object)s.Value,
+            (ArrayRemoteValue a, Type t) when t.IsArray
+                => ConvertRemoteValuesToArray<TResult>(a.Value, t.GetElementType()!),
+            (ArrayRemoteValue a, Type t) when t.IsGenericType && t.IsAssignableFrom(typeof(List<>).MakeGenericType(t.GetGenericArguments()[0]))
+                => ConvertRemoteValuesToGenericList<TResult>(a.Value, typeof(List<>).MakeGenericType(t.GetGenericArguments()[0])),
+
+            _ => throw new BiDiException($"Cannot convert {GetType().Name} to {typeof(TResult).FullName}")
  };
-    }
Evidence
The compatibility rule requires avoiding user-visible breaking behavior without compatibility
provisions. The new implicit conversions route through ConvertTo(), which throws BiDiException
for unsupported target types.

AGENTS.md
dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[57-61]
dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[106-107]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`RemoteValue.ConvertTo&amp;amp;amp;amp;amp;amp;lt;TResult&amp;amp;amp;amp;amp;amp;gt;()` throws `BiDiException` for unsupported conversions, and implicit operators now depend on `ConvertTo`, potentially changing exception behavior in a user-visible way.
## Issue Context
The project has a compliance requirement to preserve backward-compatible public API/behavior unless changes are deprecated with guidance first.
## Fix Focus Areas
- dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[57-61]
- dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[63-107]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Integer rounding semantics 🐞 Bug ✓ Correctness
Description
NumberRemoteValue -> integral conversions use Convert.ToInt32/ToInt64/etc (rounding semantics). If
truncation was expected (typical explicit cast behavior), non-integer values may convert
differently; if rounding is intended, it should be documented and tested at .5 boundaries to lock in
behavior.
Code

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[R83-94]

+            (NumberRemoteValue n, Type t) when t == typeof(short)
+                => (TResult)(object)Convert.ToInt16(n.Value),
+            (NumberRemoteValue n, Type t) when t == typeof(ushort)
+                => (TResult)(object)Convert.ToUInt16(n.Value),
+            (NumberRemoteValue n, Type t) when t == typeof(int)
+                => (TResult)(object)Convert.ToInt32(n.Value),
+            (NumberRemoteValue n, Type t) when t == typeof(uint)
+                => (TResult)(object)Convert.ToUInt32(n.Value),
+            (NumberRemoteValue n, Type t) when t == typeof(long)
+                => (TResult)(object)Convert.ToInt64(n.Value),
+            (NumberRemoteValue n, Type t) when t == typeof(ulong)
+                => (TResult)(object)Convert.ToUInt64(n.Value),
Evidence
The implementation uses Convert.ToXxx for integral conversions. Current tests only validate a
non-boundary value (5.1 -> 5) and do not clarify intended behavior at rounding boundaries like x.5.

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[83-94]
dotnet/test/common/BiDi/Script/RemoteValueConversionTest.cs[92-103]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Integral conversions from `NumberRemoteValue.Value` currently use `Convert.ToInt32/ToInt64/...`, which rounds. This is a semantic choice that can surprise users for non-integer values and should be explicitly locked-in.
### Issue Context
The conversion API is consumed via `ScriptModule.EvaluateAsync&amp;amp;amp;amp;amp;amp;lt;TResult&amp;amp;amp;amp;amp;amp;gt;()` / `CallFunctionAsync&amp;amp;amp;amp;amp;amp;lt;TResult&amp;amp;amp;amp;amp;amp;gt;()` and implicit casts.
### Fix Focus Areas
- dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[83-98]
- dotnet/test/common/BiDi/Script/RemoteValueConversionTest.cs[63-146]
### Suggested approach
- If rounding is intended: add tests for boundary values (e.g., 4.5, 5.5) and a short comment in code explaining why rounding is chosen.
- If truncation is intended: replace Convert.ToXxx with truncation-based conversion (e.g., `checked((int)Math.Truncate(n.Value))`) and update/add tests accordingly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Reflection-heavy collection conversion 🐞 Bug ➹ Performance
Description
Array/list conversions perform reflection (GetMethod/MakeGenericMethod) and per-element
MethodInfo.Invoke, which can be a performance hotspot when scripts return large arrays and
conversions are frequent.
Code

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[R116-124]

+        var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);
+        var items = remoteValues.ToList();
+        var array = Array.CreateInstance(elementType, items.Count);
+
+        for (int i = 0; i < items.Count; i++)
  {
-            return (TResult)(((StringRemoteValue)this).Value as object);
+            var convertedItem = convertMethod.Invoke(items[i], null);
+            array.SetValue(convertedItem, i);
  }
-        else if (type is object)
Evidence
The implementation uses reflection and Invoke inside loops for each converted element; this is
substantially slower than direct generic calls and is on the main path for converting
ArrayRemoteValue results.

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[116-124]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Collection conversion currently relies on reflection (`GetMethod`/`MakeGenericMethod`) and per-element `Invoke`, which is slow for large results.
### Issue Context
This path is hit when consumers call `EvaluateAsync&amp;amp;amp;amp;amp;amp;lt;int[]&amp;amp;amp;amp;amp;amp;gt;()` or `EvaluateAsync&amp;amp;amp;amp;amp;amp;lt;IEnumerable&amp;amp;amp;amp;amp;amp;lt;int&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;gt;()` and the remote value is an `ArrayRemoteValue`.
### Fix Focus Areas
- dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[109-146]
### Possible optimizations
- Cache `MethodInfo` or a compiled delegate keyed by `elementType` (e.g., `ConcurrentDictionary&amp;amp;amp;amp;amp;amp;lt;Type, Func&amp;amp;amp;amp;amp;amp;lt;RemoteValue, object?&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;gt;`).
- Keep the current behavior but reduce overhead by reusing cached conversion logic across calls.
- Ensure any refactor preserves the same exception behavior (and unwraps invocation exceptions per the other finding).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

5. // TODO lacks rationale📘 Rule violation ✓ Correctness
Description
The comment // TODO: extend types states a future action but not the reason/constraints, which
reduces maintainability. Update it to capture the intent (e.g., parity goal/issue link) or remove it
if redundant.
Code

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[R63-64]

+    // TODO: extend types
+    public TResult? ConvertTo<TResult>()
Evidence
The checklist asks that comments explain 'why' rather than 'what'. The added TODO comment does not
provide rationale (e.g., parity target, known limitation, or tracking issue).

AGENTS.md
dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[63-64]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A newly added comment states a TODO without explaining the rationale/intent.
## Issue Context
Comments should capture intent/constraints (the &amp;amp;amp;amp;amp;amp;#x27;why&amp;amp;amp;amp;amp;amp;#x27;) rather than restating actions.
## Fix Focus Areas
- dotnet/src/webdriver/BiDi/Script/RemoteValue.cs[63-64]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@nvborisenko
Copy link
Copy Markdown
Member Author

Long awaited feature. Let's move on with some useful primitives.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds richer .NET-side conversions for BiDi Script.RemoteValue, enabling consumers to convert returned BiDi values into common CLR primitive/collection types.

Changes:

  • Introduces RemoteValue.ConvertTo<TResult>() and updates implicit conversions to delegate to it.
  • Adds array and generic enumerable/list conversions for ArrayRemoteValue.
  • Adds unit tests covering conversions for primitives, nulls, arrays, and common collection interfaces.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
dotnet/src/webdriver/BiDi/Script/RemoteValue.cs Implements ConvertTo<TResult>(), updates implicit operators, and adds array/list conversion helpers.
dotnet/test/common/BiDi/Script/RemoteValueConversionTest.cs New unit tests validating conversions from RemoteValue to CLR primitives and collections.

Comment thread dotnet/src/webdriver/BiDi/Script/RemoteValue.cs
Comment thread dotnet/src/webdriver/BiDi/Script/RemoteValue.cs
Comment thread dotnet/src/webdriver/BiDi/Script/RemoteValue.cs Outdated
Comment thread dotnet/test/common/BiDi/Script/RemoteValueConversionTest.cs Outdated
Comment thread dotnet/test/common/BiDi/Script/RemoteValueConversionTest.cs Outdated
Comment thread dotnet/src/webdriver/BiDi/Script/RemoteValue.cs
Copilot AI review requested due to automatic review settings March 3, 2026 20:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

Comment thread dotnet/src/webdriver/BiDi/Script/RemoteValue.cs Outdated
Comment thread dotnet/src/webdriver/BiDi/Script/RemoteValue.cs
Comment thread dotnet/src/webdriver/BiDi/Script/RemoteValue.cs
Comment thread dotnet/src/webdriver/BiDi/Script/RemoteValue.cs
Comment thread dotnet/test/common/BiDi/Script/RemoteValueConversionTests.cs Outdated
Comment thread dotnet/test/common/BiDi/Script/RemoteValueConversionTests.cs Outdated
Comment thread dotnet/test/common/BiDi/Script/RemoteValueConversionTests.cs Outdated
Copilot AI review requested due to automatic review settings March 3, 2026 20:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

Comment thread dotnet/test/common/BiDi/Script/RemoteValueConversionTests.cs
Comment thread dotnet/src/webdriver/BiDi/Script/RemoteValue.cs
Comment thread dotnet/src/webdriver/BiDi/Script/RemoteValue.cs
Comment thread dotnet/src/webdriver/BiDi/Script/RemoteValue.cs
This was referenced Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants