microsoft / microsoft/playwright-dotnet
[FEATURE] EvaluateAsync deserializes correctly to a class, but not to a record without parameterless constructor
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 3k
- Forks
- 304
- Avg merge
- 20h 47m
- Merged PRs (30d)
- 6
Description
System info
- Playwright Version: [v1.37.1] (also reproduced in clone of latest main branch)
- Operating System: Windows 10
- Browser: Chromium
Source code
The crux of the issue is that Page.EvaluateAsync does not deserialize to a record and throws an exception. Deserializing to a class with the same members works without issue. See tests below:
Working deserialization of EvaluateAsync
This one works - it deserializes to the indicated class.
private class TestClass
{
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("count")]
public int Count { get; set; }
}
[TestMethod]
public async Task DeserializeToClassTest()
{
await Page.GotoAsync("https://www.github.com");
var jsExpr = """
var doWork = function() {
var val = {"name": "Playwright", "count": 5};
return val;
}
doWork();
""";
var result = await Page.EvaluateAsync<TestClass>(jsExpr);
Assert.AreEqual("Playwright", result.Name);
Assert.AreEqual(5, result.Count);
}
Not working deserialization of EvaluateAsync
This one will fail to work and throws the following exception:
Expecting My.Tests.Selectors.ParserTests+TestRecord, got Object
at Microsoft.Playwright.Transport.Converters.EvaluateArgumentValueConverter.ToExpectedType(Object parsed, Type t, IDictionary2 visited) in /_/src/Playwright/Transport/Converters/EvaluateArgumentValueConverter.cs:line 246 at Microsoft.Playwright.Transport.Converters.EvaluateArgumentValueConverter.Deserialize(JsonElement result, Type t) in /_/src/Playwright/Transport/Converters/EvaluateArgumentValueConverter.cs:line 211 at Microsoft.Playwright.Core.ScriptsHelper.ParseEvaluateResult[T](Nullable1 resultOrNull) in //src/Playwright/Core/ScriptsHelper.cs:line 61
at Microsoft.Playwright.Core.Frame.EvaluateAsync[T](String script, Object arg) in //src/Playwright/Core/Frame.cs:line 555
at Scraper.Tests.Selectors.ParserTests.DeserializeToRecordTest() in G:\My.Tests\Selectors\ParserTests.cs:line 130
at Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.ThreadOperations.ExecuteWithAbortSafety(Action action)
private record TestRecord([property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("count")] int Count);
[TestMethod]
public async Task DeserializeToRecordTest()
{
await Page.GotoAsync("https://www.github.com");
var jsExpr = """
var doWork = function() {
var val = {"name": "Playwright", "count": 5};
return val;
}
doWork();
""";
var result = await Page.EvaluateAsync<TestRecord>(jsExpr);
Assert.AreEqual("Playwright", result.Name);
Assert.AreEqual(5, result.Count);
}
** Steps **
- Paste the above snippets into a test class
- Run the test and observe
DeserializeToClassTestpass andDeserializeToRecordTestfail.
Expected
Both tests should pass and produce their respective class and record types with the deserialized values.
Actual
The class deserialization test works. The record deserialization test does not work and throws an exception.
Having played around with it a little bit, the issue appears to be the use of the init setters on the record. If one creates the TestRecord with the following signature instead:
private record TestRecord
{
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("count")]
public int Count { get; set; }
}
Deserialization will now work precisely as intended. However, given that a lot of record usage promotes using the implicit properties via the record constructor, this is an issue that should be remedied as I expect most developers would expect classes and records to be largely interchangeable and that they wouldn't have to change setter types to make deserialization work (as that's not typically something you have to do with System.Text.Json deserialization anyway).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the class and positional-record cases described in the issue, then inspect src/Playwright/Transport/Converters/EvaluateArgumentValueConverter.cs around line 246 and the evaluation path in src/Playwright/Core/ScriptsHelper.cs. Add a regression test covering EvaluateAsync with a record constructor and verify that both the class and record cases return the expected Name and Count values.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- testing-qa
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100