Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dotnet] [bidi] Align Scipt.LocalValue.Map with spec, enable negative zero #15395

Open
wants to merge 30 commits into
base: trunk
Choose a base branch
from

Conversation

RenderMichael
Copy link
Contributor

@RenderMichael RenderMichael commented Mar 9, 2025

User description

Fixes using the Script.LocalValue.Map type and the related return type. The spec describes it the same as Object:
image

So this is now a "clone" of LocalValue.Object.
 
Additionally, this PR deserializes negative zero from a string, in alignment with the spec:
image

Motivation and Context

Fixes #15394

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • I have read the contributing document.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

PR Type

Bug fix, Tests


Description

  • Fixed Script.LocalValue.Map to align with the WebDriver BiDi spec.

  • Updated RemoteValue.Map to use IReadOnlyList<IReadOnlyList<RemoteValue>>.

  • Added comprehensive tests for LocalValue and RemoteValue roundtrip scenarios.

  • Enhanced JSON serialization options to handle named floating-point literals.


Changes walkthrough 📝

Relevant files
Enhancement
Broker.cs
Enhanced JSON serialization options.                                         

dotnet/src/webdriver/BiDi/Communication/Broker.cs

  • Added support for named floating-point literals in JSON serialization.
  • +1/-0     
    Bug fix
    LocalValue.cs
    Updated `LocalValue.Map` structure.                                           

    dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs

  • Changed Map to use IEnumerable>.
  • Ensured alignment with Object serialization structure.
  • +1/-1     
    RemoteValue.cs
    Modified `RemoteValue.Map` structure.                                       

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

  • Updated Map to use IReadOnlyList>.
  • Ensured compatibility with the WebDriver BiDi spec.
  • +1/-1     
    Tests
    CallFunctionParameterTest.cs
    Added comprehensive tests for `LocalValue` and `RemoteValue`.

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

  • Added new test cases for LocalValue and RemoteValue roundtrip
    scenarios.
  • Verified serialization and deserialization of various LocalValue
    types.
  • Tested Map and Object structures for compliance with the spec.
  • +139/-0 

    Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • Sorry, something went wrong.

    Copy link
    Contributor

    qodo-merge-pro bot commented Mar 9, 2025

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    🎫 Ticket compliance analysis ✅

    15394 - Fully compliant

    Compliant requirements:

    • Fix BiDi Script.LocalValue.Map to be passable to the Script.CallFunctionAsync command
    • Align Map serialization with the WebDriver BiDi spec, which states it should be serialized the same way as Object
    • Fix related RemoteValue.Map type to match the spec

    ⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
    🧪 PR contains tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Commented Test Cases

    There are two commented-out test cases for Boolean values (true/false) in the RoundtripOptions method. These should either be uncommented or removed if they're not needed.

    //yield return new TestCaseData(new LocalValue.Boolean(true), new RemoteValue.Boolean(true), "typeof arg === true")
    //{
    //    TestName = nameof(CanCallFunctionAndRoundtrip_Five) + "(true)",
    //};
    //yield return new TestCaseData(new LocalValue.Boolean(false), new RemoteValue.Boolean(false), "typeof arg === false")
    //{
    //    TestName = nameof(CanCallFunctionAndRoundtrip_Five) + "(false)",
    //};

    Copy link
    Contributor

    qodo-merge-pro bot commented Mar 9, 2025

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Fix boolean assertion logic
    Suggestion Impact:The suggestion pointed out a logical error in the JavaScript assertion for boolean values. The commit completely restructured the test approach, removing the problematic code that used the incorrect assertion pattern. The commented-out test cases with the incorrect assertions were removed entirely in the commit.

    code diff:

    -        //yield return new TestCaseData(new LocalValue.Boolean(true), new RemoteValue.Boolean(true), "typeof arg === true")
    -        //{
    -        //    TestName = nameof(CanCallFunctionAndRoundtrip_Five) + "(true)",
    -        //};
    -        //yield return new TestCaseData(new LocalValue.Boolean(false), new RemoteValue.Boolean(false), "typeof arg === false")
    -        //{
    -        //    TestName = nameof(CanCallFunctionAndRoundtrip_Five) + "(false)",
    -        //};

    The JavaScript assertion for Boolean values is incorrect. The typeof operator
    returns the string "boolean" for boolean values, and you're comparing it
    directly to true/false. You should either remove the typeof or change the
    assertion to check the value directly.

    dotnet/test/common/BiDi/Script/CallFunctionParameterTest.cs [275-282]

    -//yield return new TestCaseData(new LocalValue.Boolean(true), new RemoteValue.Boolean(true), "typeof arg === true")
    +//yield return new TestCaseData(new LocalValue.Boolean(true), new RemoteValue.Boolean(true), "arg === true")
     //{
     //    TestName = nameof(CanCallFunctionAndRoundtrip_Five) + "(true)",
     //};
    -//yield return new TestCaseData(new LocalValue.Boolean(false), new RemoteValue.Boolean(false), "typeof arg === false")
    +//yield return new TestCaseData(new LocalValue.Boolean(false), new RemoteValue.Boolean(false), "arg === false")
     //{
     //    TestName = nameof(CanCallFunctionAndRoundtrip_Five) + "(false)",
     //};

    [Suggestion has been applied]

    Suggestion importance[1-10]: 8

    __

    Why: The suggestion correctly identifies a logical error in the commented-out test cases. The JavaScript assertion "typeof arg === true" is incorrect as typeof would return the string "boolean", not the boolean value itself. This would cause the tests to fail if uncommented.

    Medium
    • Update

    Sorry, something went wrong.

    @RenderMichael RenderMichael changed the title [dotnet] Align Scipt.LocalValue.Map with spec [dotnet] [bidi] Align Scipt.LocalValue.Map with spec Mar 9, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    @nvborisenko
    Copy link
    Member

    We learned:

    • double.NegativeZero is a .NET type for -0
    • Special converters should be applied for special cases only

    I propose to gather how vendors serialize special numbers in JSON, then it will be easier for us to handle.

    @RenderMichael
    Copy link
    Contributor Author

    For de-serializing a -0:
    await context.Script.CallFunctionAsync("() => { return -0; }", false);

    • Firefox: {"type":"number","value":"-0"}
    • Chrome: {"type":"number","value":"-0"}

    For serializing a -0:

    var minus0 = new LocalValue.Number(double.NegativeZero);
    
    await context.Script.CallFunctionAsync($$"""
        (arg) => {
            if (arg !== 0 || arg.toLocaleString()[0] !== '-') {
            throw new Error("Assert failed: " + arg.toLocaleString());
            }
        }
        """, false, new() { Arguments = [minus0] });

    When arguments are serialized as "arguments":[{"type":"number","value":-0}]

    • On Firefox: Error: Assert failed: 0 (in other words, it thinks the value is regular 0)
    • On Chrome: Error: Assert failed: 0 (same as above)

    When arguments are serialized as "arguments":[{"type":"number","value":"-0"}]

    • On Firefox: No exception is thrown ("is negative zero" check passed)
    • On Chrome: No exception is thrown

    Conclusion

    Across the board, for serializing and deserializing, on all browsers, negative zero is expected to be a string "-0"

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    @nvborisenko
    Copy link
    Member

    When arguments are serialized as "arguments":[{"type":"number","value":-0}] On Firefox: Error: Assert failed: 0 (in other words, it thinks the value is regular 0)

    On my Firefox your test passes when we {"type":"number","value":-0}

    @nvborisenko
    Copy link
    Member

    OK, so in Firefox everything works good without any custom double converters. Chromium cannot handle -0 and everything works good.

    I posted w3c/webdriver-bidi#887 issue in BiDi spec. I propose to not introduce custom converter at this time, and move forward with ignored unit tests (for chromium only).

    PS: I was wrong, spec requires -0 to be string:

    [Type](https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values)(value) is Number
    Switch on the value of value:
    
    NaN
    Let serialized be "NaN"
    -0
    Let serialized be "-0"
    

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    """, false);

    Assert.That(response.Result, Is.AssignableTo<RemoteValue.Set>());
    Assert.That(((RemoteValue.Set)response.Result).Value, Is.EqualTo(expected.Value));
    Copy link
    Member

    Choose a reason for hiding this comment

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

    Here I don't trust to "magic of records". Let's be simple: assert this, assert that.

    Copy link
    Member

    Choose a reason for hiding this comment

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

    Ah, again I am 50/50, "magic of records" works

    Copy link
    Contributor Author

    Choose a reason for hiding this comment

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

    I removed the magic from the primitive types (undefined, null, bool, number, string).

    However, I kept it for all the other types: they all have other properties. We can verify not just the value, but also the handle and internal ID. Records + NUnit = full validation.

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    @RenderMichael
    Copy link
    Contributor Author

    Formatting build failure unrelated:

    --- a/py/test/selenium/webdriver/common/selenium_manager_tests.py
    +++ b/py/test/selenium/webdriver/common/selenium_manager_tests.py

    Test failures not related:

    //py:common-chrome-bidi-test/selenium/webdriver/common/bidi_tests.py
    //py:common-edge-bidi-test/selenium/webdriver/common/bidi_tests.py
    //rb/spec/integration/selenium/webdriver:action_builder-chrome-remote
    //rb/spec/integration/selenium/webdriver:driver-chrome
    //rb/spec/integration/selenium/webdriver:driver-chrome-remote
    //rb/spec/integration/selenium/webdriver:driver-firefox-beta
    //rb/spec/integration/selenium/webdriver:manager-chrome
    //rb/spec/integration/selenium/webdriver:select-chrome
    //rb/spec/integration/selenium/webdriver:select-chrome-remote

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    [🐛 Bug]: [dotnet] BiDi LocalValue.Map cannot be passed to the Script.CallFunctionAsync command
    2 participants