Skip to content

Commit 3e7c4df

Browse files
authored
Implement Page.evaluate Feature (#94)
1 parent 76a021d commit 3e7c4df

15 files changed

+314
-51
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Xunit;
5+
6+
namespace PuppeteerSharp.Tests.Page
7+
{
8+
[Collection("PuppeteerLoaderFixture collection")]
9+
public class EvaluateTests : PuppeteerBaseTest
10+
{
11+
[Theory]
12+
[InlineData("1 + 5;", 6)] //ShouldAcceptSemiColons
13+
[InlineData("2 + 5\n// do some math!'", 7)] //ShouldAceptStringComments
14+
public async Task BasicIntExressionEvaluationTest(string script, object expected)
15+
{
16+
using (var page = await Browser.NewPageAsync())
17+
{
18+
var result = await page.EvaluateExpressionAsync<int>(script);
19+
Assert.Equal(expected, result);
20+
}
21+
}
22+
23+
[Theory]
24+
[InlineData("() => 7 * 3", 21)] //ShouldWork
25+
[InlineData("() => Promise.resolve(8 * 7)", 56)] //ShouldAwaitPromise
26+
public async Task BasicIntFunctionEvaluationTest(string script, object expected)
27+
{
28+
using (var page = await Browser.NewPageAsync())
29+
{
30+
var result = await page.EvaluateFunctionAsync<int>(script);
31+
Assert.Equal(expected, result);
32+
}
33+
}
34+
35+
[Fact]
36+
public async Task ShouldWorkRightAfterFrameNavigated()
37+
{
38+
Task<int> frameEvaluation = null;
39+
40+
using (var page = await Browser.NewPageAsync())
41+
{
42+
page.FrameNavigated += (sender, e) =>
43+
{
44+
frameEvaluation = e.Frame.EvaluateFunctionAsync<int>("() => 6 * 7");
45+
};
46+
47+
await page.GoToAsync(TestConstants.EmptyPage);
48+
Assert.Equal(42, await frameEvaluation);
49+
}
50+
}
51+
52+
[Fact]
53+
public async Task ShouldRejectPromiseWithExeption()
54+
{
55+
using (var page = await Browser.NewPageAsync())
56+
{
57+
var exception = await Assert.ThrowsAsync<EvaluationFailedException>(() =>
58+
{
59+
return page.EvaluateFunctionAsync<object>("() => not.existing.object.property");
60+
});
61+
62+
Assert.Contains("not is not defined", exception.Message);
63+
}
64+
}
65+
66+
[Fact]
67+
public async Task SouldReturnComplexObjects()
68+
{
69+
using (var page = await Browser.NewPageAsync())
70+
{
71+
dynamic obj = new
72+
{
73+
foo = "bar!"
74+
};
75+
dynamic result = await page.EvaluateFunctionAsync("a => a", obj);
76+
Assert.Equal("bar!", result.foo.ToString());
77+
}
78+
}
79+
80+
[Theory]
81+
[InlineData("() => NaN", double.NaN)] //ShouldReturnNaN
82+
[InlineData("() => -0", -0)] //ShouldReturnNegative0
83+
[InlineData("() => Infinity", double.PositiveInfinity)] //ShouldReturnInfinity
84+
[InlineData("() => -Infinity", double.NegativeInfinity)] //ShouldReturnNegativeInfinty
85+
public async Task BasicEvaluationTest(string script, object expected)
86+
{
87+
using (var page = await Browser.NewPageAsync())
88+
{
89+
dynamic result = await page.EvaluateFunctionAsync(script);
90+
Assert.Equal(expected, result);
91+
}
92+
}
93+
94+
[Fact]
95+
public async Task ShouldAcceptNullAsOneOfMultipleParameters()
96+
{
97+
using (var page = await Browser.NewPageAsync())
98+
{
99+
bool result = await page.EvaluateFunctionAsync<bool>("(a, b) => Object.is(a, null) && Object.is(b, 'foo')", null, "foo");
100+
Assert.True(result);
101+
}
102+
}
103+
104+
[Fact]
105+
public async Task ShouldProperlyIgnoreUndefinedFields()
106+
{
107+
using (var page = await Browser.NewPageAsync())
108+
{
109+
var result = await page.EvaluateFunctionAsync<Dictionary<string, object>>("() => ({a: undefined})");
110+
Assert.Empty(result);
111+
}
112+
}
113+
114+
[Fact]
115+
public async Task ShouldProperlySerializeNullFields()
116+
{
117+
using (var page = await Browser.NewPageAsync())
118+
{
119+
var result = await page.EvaluateFunctionAsync<Dictionary<string, object>>("() => ({a: null})");
120+
Assert.True(result.ContainsKey("a"));
121+
Assert.Null(result["a"]);
122+
}
123+
}
124+
}
125+
}

lib/PuppeteerSharp.Tests/Puppeteer/PuppeteerLaunchTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public async Task ShouldRejectAllPromisesWhenBrowserIsClosed()
6565
TestConstants.ChromiumRevision))
6666
using (var page = await browser.NewPageAsync())
6767
{
68-
var neverResolves = page.EvaluateHandle("() => new Promise(r => {})");
68+
var neverResolves = page.EvaluateFunctionHandle("() => new Promise(r => {})");
6969
await browser.CloseAsync();
7070

7171
await neverResolves;

lib/PuppeteerSharp/ContextPayloadAuxData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ public struct ContextPayloadAuxData
55
public string FrameId { get; set; }
66
public bool IsDefault { get; set; }
77
}
8-
}
8+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Newtonsoft.Json;
2+
3+
namespace PuppeteerSharp
4+
{
5+
public class EvaluateExceptionDetails
6+
{
7+
[JsonProperty("exception")]
8+
public EvaluateExceptionInfo Exception { get; internal set; }
9+
[JsonProperty("text")]
10+
public string Text { get; internal set; }
11+
[JsonProperty("stackTrace")]
12+
public EvaluateExceptionStackTrace StackTrace { get; internal set; }
13+
}
14+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Newtonsoft.Json;
2+
3+
namespace PuppeteerSharp
4+
{
5+
public class EvaluateExceptionInfo
6+
{
7+
[JsonProperty("description")]
8+
public string Description { get; internal set; }
9+
}
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Collections.Generic;
2+
using Newtonsoft.Json;
3+
4+
namespace PuppeteerSharp
5+
{
6+
public class EvaluateExceptionStackTrace
7+
{
8+
[JsonProperty("callFrames")]
9+
public EvaluationExceptionCallFrame[] CallFrames { get; internal set; }
10+
}
11+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Newtonsoft.Json;
2+
3+
namespace PuppeteerSharp
4+
{
5+
public class EvaluationExceptionCallFrame
6+
{
7+
[JsonProperty("columnNumber")]
8+
public int ColumnNumber { get; internal set; }
9+
[JsonProperty("lineNumber")]
10+
public int LineNumber { get; internal set; }
11+
[JsonProperty("url")]
12+
public string Url { get; internal set; }
13+
[JsonProperty("functionName")]
14+
public string FunctionName { get; internal set; }
15+
}
16+
}

lib/PuppeteerSharp/EvaluationFailedException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace PuppeteerSharp
55
{
66
[Serializable]
7-
internal class EvaluationFailedException : Exception
7+
public class EvaluationFailedException : Exception
88
{
99
public EvaluationFailedException()
1010
{

lib/PuppeteerSharp/ExceptionInfo.cs

Lines changed: 0 additions & 7 deletions
This file was deleted.

lib/PuppeteerSharp/ExecutionContext.cs

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Threading.Tasks;
4+
using System.Linq;
5+
using Newtonsoft.Json.Linq;
46

57
namespace PuppeteerSharp
68
{
@@ -22,44 +24,89 @@ public ExecutionContext(Session client, ContextPayload contextPayload, Func<dyna
2224
public string FrameId { get; internal set; }
2325
public bool IsDefault { get; internal set; }
2426

25-
public async Task<dynamic> Evaluate(string pageFunction, params object[] args)
27+
public async Task<dynamic> EvaluateExpressionAsync(string script)
2628
{
27-
var handle = await EvaluateHandleAsync(pageFunction, args);
29+
var handle = await EvaluateExpressionHandleAsync(script);
2830
dynamic result = await handle.JsonValue();
2931
await handle.Dispose();
3032
return result;
3133
}
3234

33-
internal async Task<JSHandle> EvaluateHandleAsync(Func<object> pageFunction, object[] args)
35+
public async Task<T> EvaluateExpressionAsync<T>(string script)
3436
{
35-
throw new NotImplementedException();
37+
var result = await EvaluateExpressionAsync(script);
38+
return ((JToken)result).ToObject<T>();
3639
}
3740

38-
internal async Task<JSHandle> EvaluateHandleAsync(string pageFunction, object[] args)
41+
public async Task<dynamic> EvaluateFunctionAsync(string script, params object[] args)
3942
{
40-
if (!string.IsNullOrEmpty(pageFunction))
43+
var handle = await EvaluateFunctionHandleAsync(script, args);
44+
dynamic result = await handle.JsonValue();
45+
await handle.Dispose();
46+
return result;
47+
}
48+
49+
public async Task<T> EvaluateFunctionAsync<T>(string script, params object[] args)
50+
{
51+
var result = await EvaluateFunctionAsync(script, args);
52+
return ((JToken)result).ToObject<T>();
53+
}
54+
55+
internal async Task<JSHandle> EvaluateExpressionHandleAsync(string script)
56+
{
57+
if (string.IsNullOrEmpty(script))
4158
{
42-
dynamic remoteObject;
59+
return null;
60+
}
61+
62+
dynamic remoteObject;
4363

44-
try
64+
try
65+
{
66+
remoteObject = await _client.SendAsync("Runtime.evaluate", new Dictionary<string, object>()
4567
{
46-
remoteObject = await _client.SendAsync("Runtime.evaluate", new Dictionary<string, object>()
47-
{
48-
["expression"] = pageFunction,
49-
["contextId"] = _contextId,
50-
["returnByValue"] = false,
51-
["awaitPromise"] = true
52-
});
53-
54-
return ObjectHandleFactory(remoteObject.result);
55-
}
56-
catch (Exception ex)
68+
{"contextId", _contextId},
69+
{"expression", script},
70+
{"returnByValue", false},
71+
{"awaitPromise", true}
72+
});
73+
74+
return ObjectHandleFactory(remoteObject.result);
75+
}
76+
catch (Exception ex)
77+
{
78+
throw new EvaluationFailedException("Evaluation Failed", ex);
79+
}
80+
}
81+
82+
internal async Task<JSHandle> EvaluateFunctionHandleAsync(string script, object[] args)
83+
{
84+
if (string.IsNullOrEmpty(script))
85+
{
86+
return null;
87+
}
88+
89+
dynamic response = await _client.SendAsync("Runtime.callFunctionOn", new Dictionary<string, object>()
5790
{
58-
throw new EvaluationFailedException("Evaluation Failed", ex);
59-
}
91+
{"functionDeclaration", script },
92+
{"executionContextId", _contextId},
93+
{"arguments", FormatArguments(args)},
94+
{"returnByValue", false},
95+
{"awaitPromise", true}
96+
});
97+
98+
if (response.exceptionDetails != null)
99+
{
100+
throw new EvaluationFailedException("Evaluation failed: " +
101+
Helper.GetExceptionMessage(response.exceptionDetails.ToObject<EvaluateExceptionDetails>()));
60102
}
61103

62-
return null;
104+
return ObjectHandleFactory(response.result);
105+
}
106+
107+
private object FormatArguments(object[] args)
108+
{
109+
return args.Select(o => new { value = o });
63110
}
64111

65112
public async Task<dynamic> QueryObjects(JSHandle prototypeHandle)

0 commit comments

Comments
 (0)