Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/PlaywrightNative.Tests/JSHandlePropertiesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,53 @@ public async Task GetPropertiesShouldReturnEmptyMapForNonObjects()
Dictionary<string, IJSHandle> properties = await aHandle.GetPropertiesAsync().ConfigureAwait(false);
Assert.That(properties, Is.Empty);
}

[PlaywrightTest("jshandle-properties.spec.ts", "getProperties should return properties inhertied from super class")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public async Task GetPropertiesShouldReturnPropertiesInhertiedFromSuperClass()
{
IJSHandle aHandle = await Page.EvaluateHandleAsync(@"() => {
class A {
constructor() {
this.a = '1';
}
}
class B extends A {
constructor() {
super();
this.b = '2';
}
}
return new B();
}").ConfigureAwait(false);
Dictionary<string, IJSHandle> properties = await aHandle.GetPropertiesAsync().ConfigureAwait(false);
Assert.That(await properties["a"].JsonValueAsync<string>().ConfigureAwait(false), Is.EqualTo("1"));
Assert.That(await properties["b"].JsonValueAsync<string>().ConfigureAwait(false), Is.EqualTo("2"));
}

[PlaywrightTest("jshandle-properties.spec.ts", "getProperties should return own enumerable properties only")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public async Task GetPropertiesShouldReturnOwnEnumerablePropertiesOnly()
{
IJSHandle aHandle = await Page.EvaluateHandleAsync(@"() => {
class A {
constructor() {
this.own = 1;
Object.defineProperty(this, 'ownHidden', { value: 2, enumerable: false });
}
}
A.prototype.inherited = 3;
return new A();
}").ConfigureAwait(false);
Assert.That((await aHandle.GetPropertiesAsync().ConfigureAwait(false)).Keys, Is.EqualTo(new[] { "own" }));

await Page.SetContentAsync("<div id=d><span>a</span><span>b</span></div>").ConfigureAwait(false);
IJSHandle collection = await Page.EvaluateHandleAsync("() => document.querySelector('#d').children").ConfigureAwait(false);
Assert.That((await collection.GetPropertiesAsync().ConfigureAwait(false)).Keys, Is.EqualTo(new[] { "0", "1" }));
}

[PlaywrightTest("jshandle-properties.spec.ts", "getProperties should work with elements")]
[Test]
[Timeout(30_000)]
Expand Down
44 changes: 44 additions & 0 deletions src/PlaywrightNative.Tests/PageEvaluateParityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,21 @@ public async Task ShouldTransferMapsAsEmptyObjects()
Assert.That(result, Is.EqualTo("Object {}"));
}

[PlaywrightTest("page-evaluate.spec.ts", "should transfer sets as empty objects")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public async Task ShouldTransferSetsAsEmptyObjects()
{
// .NET has no JS Set, so build it in the page and transfer it back as the argument.
Dictionary<string, object> set = await Page.EvaluateAsync<Dictionary<string, object>>("() => new Set([1, 2])").ConfigureAwait(false);
string result = await Page.EvaluateAsync<string>(
"a => a.x.constructor.name + ' ' + JSON.stringify(a.x)",
new Dictionary<string, object> { ["x"] = set })
.ConfigureAwait(false);
Assert.That(result, Is.EqualTo("Object {}"));
Assert.That(set, Is.Empty);
}

[PlaywrightTest("page-evaluate.spec.ts", "should modify global environment")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
Expand Down Expand Up @@ -482,6 +497,35 @@ public void ShouldSupportThrownNumbersAsErrorMessages()
Assert.That(error.Message, Does.Contain("100500"));
}

[PlaywrightTest("page-evaluate.spec.ts", "should reject when a falsy value is thrown")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public void ShouldRejectWhenAFalsyValueIsThrown()
{
foreach (string value in new[] { "null", "undefined", "0", "''", "false", "NaN", "-0", "0n" })
{
Assert.CatchAsync<PlaywrightException>(
() => Page.EvaluateAsync<object>($"(() => {{ throw {value}; }})()"),
$"throw {value}");
Assert.CatchAsync<PlaywrightException>(
() => Page.EvaluateAsync<object>($"Promise.reject({value})"),
$"Promise.reject({value})");
}
}

[PlaywrightTest("page-evaluate.spec.ts", "should include a non-error rejection value in the error message")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public void ShouldIncludeANonErrorRejectionValueInTheErrorMessage()
{
PlaywrightException error = Assert.CatchAsync<PlaywrightException>(
() => Page.EvaluateAsync<object>("() => Promise.reject(7)"));
Assert.That(error.Message, Does.Contain("7"));
PlaywrightException nullError = Assert.CatchAsync<PlaywrightException>(
() => Page.EvaluateAsync<object>("() => { throw null; }"));
Assert.That(nullError.Message, Does.Contain("null"));
}

[PlaywrightTest("page-evaluate.spec.ts", "should return complex objects")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
Expand Down
26 changes: 26 additions & 0 deletions src/PlaywrightNative.Tests/PageEventConsoleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,32 @@ await Task.WhenAll(
}
}

[PlaywrightTest("page-event-console.spec.ts", "should not fail for object that can't be coerced to a primitive")]
[Test]
[Timeout(TestConstants.DefaultTestTimeout)]
public async Task ShouldNotFailForObjectThatCantBeCoercedToAPrimitive()
{
IConsoleMessage message = null;
void OnConsole(object sender, IConsoleMessage received)
{
Page.Console -= OnConsole;
message = received;
}

Page.Console += OnConsole;
await Task.WhenAll(
Page.EvaluateAsync<object>("() => console.log({ toString() { return {}; }, valueOf() { return {}; } })"),
Page.WaitForConsoleMessageAsync()).ConfigureAwait(false);
if (!TestConstants.IsFirefox)
{
Assert.That(message.Text, Does.Match(@"\{toString: (Function)?, valueOf: (Function)?\}"));
}
else
{
Assert.That(message.Text, Is.EqualTo("JSHandle@object"));
}
}

[PlaywrightTest("page-event-console.spec.ts", "should trigger correct Log")]
[Test]
[Retry(2)]
Expand Down
5 changes: 5 additions & 0 deletions src/PlaywrightNative/Chromium/CRExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,11 @@ private static void ThrowIfExceptionDetails(JsonElement response)
{
message = description.GetString();
}
else if (!exception.TryGetProperty("value", out JsonElement rawValue) || rawValue.ValueKind == JsonValueKind.Null)
{
// Official getExceptionMessage: String(exception.value) for thrown null / undefined.
message = rawValue.ValueKind == JsonValueKind.Null ? "null" : "undefined";
}

if (exception.TryGetProperty("value", out JsonElement thrown)
&& thrown.ValueKind != JsonValueKind.Undefined
Expand Down
8 changes: 4 additions & 4 deletions src/PlaywrightNative/Helpers/JsonValueHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ internal static class JsonValueHelper
internal const string SerializeFunction = EvaluateSerialization.SerializeJs;

/// <summary>
/// Returns own and inherited (prototype) property names, or an empty list for
/// primitives / <c>null</c>. Stops at <c>Object.prototype</c> so <c>getProperties</c>
/// includes constructor-assigned parent fields without walking built-ins.
/// Returns own enumerable data property names, or an empty list for
/// primitives / <c>null</c>. Matches the protocol <c>getProperties</c>
/// filter (<c>ownProperties</c>, enumerable, has a value).
/// </summary>
internal const string EnumerablePropertyNamesFunction =
"o => { if (o === null || o === undefined || (typeof o !== 'object' && typeof o !== 'function')) return []; const names = []; const seen = new Set(); for (let obj = o; obj && obj !== Object.prototype; obj = Object.getPrototypeOf(obj)) { const own = Object.getOwnPropertyNames(obj); for (let i = 0; i < own.length; i++) { const name = own[i]; if (!seen.has(name)) { seen.add(name); names.push(name); } } } return names; }";
"o => { if (o === null || o === undefined || (typeof o !== 'object' && typeof o !== 'function')) return []; return Object.keys(o).filter(name => { const descriptor = Object.getOwnPropertyDescriptor(o, name); return !!descriptor && 'value' in descriptor; }); }";

private static readonly JsonSerializerOptions PreserveOptions = new JsonSerializerOptions
{
Expand Down
Loading