Skip to content

Commit 5858143

Browse files
PhenXkblok
authored andcommitted
Implement Console Events and Page.close tests (#134)
1 parent d5dd8da commit 5858143

File tree

15 files changed

+300
-69
lines changed

15 files changed

+300
-69
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Threading.Tasks;
2+
using Xunit;
3+
4+
namespace PuppeteerSharp.Tests.Page
5+
{
6+
[Collection("PuppeteerLoaderFixture collection")]
7+
public class CloseTests : PuppeteerBaseTest
8+
{
9+
[Fact]
10+
public async Task ShouldRejectAllPromisesWhenPageIsClosed()
11+
{
12+
var newPage = await Browser.NewPageAsync();
13+
var neverResolves = newPage.EvaluateFunctionAsync("() => new Promise(r => {})");
14+
15+
// Put into a var to avoid warning
16+
var t = newPage.CloseAsync();
17+
18+
var exception = await Assert.ThrowsAsync<TargetClosedException>(async () => await neverResolves);
19+
20+
Assert.Contains("Protocol error", exception.Message);
21+
}
22+
}
23+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using System.Linq;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Newtonsoft.Json.Linq;
5+
using Xunit;
6+
7+
namespace PuppeteerSharp.Tests.Page.Events
8+
{
9+
[Collection("PuppeteerLoaderFixture collection")]
10+
public class ConsoleTests : PuppeteerPageBaseTest
11+
{
12+
[Fact]
13+
public async Task ShouldWork()
14+
{
15+
ConsoleMessage message = null;
16+
17+
void EventHandler(object sender, ConsoleEventArgs e)
18+
{
19+
message = e.Message;
20+
Page.Console -= EventHandler;
21+
}
22+
23+
Page.Console += EventHandler;
24+
25+
await Page.EvaluateExpressionAsync("console.log('hello', 5, {foo: 'bar'})");
26+
27+
var obj = new Dictionary<string, object> {{"foo", "bar"}};
28+
29+
Assert.Equal("hello 5 JSHandle@object", message.Text);
30+
Assert.Equal(ConsoleType.Log, message.Type);
31+
32+
Assert.Equal("hello", await message.Args[0].JsonValue());
33+
Assert.Equal(5, await message.Args[1].JsonValue<float>());
34+
Assert.Equal(obj, await message.Args[2].JsonValue<Dictionary<string, object>>());
35+
Assert.Equal("bar", (await message.Args[2].JsonValue<dynamic>()).foo.ToString());
36+
}
37+
38+
[Fact]
39+
public async Task ShouldWorkForDifferentConsoleApiCalls()
40+
{
41+
var messages = new List<ConsoleMessage>();
42+
43+
Page.Console += (sender, e) => messages.Add(e.Message);
44+
45+
await Page.EvaluateFunctionAsync(@"() => {
46+
// A pair of time/timeEnd generates only one Console API call.
47+
console.time('calling console.time');
48+
console.timeEnd('calling console.time');
49+
console.trace('calling console.trace');
50+
console.dir('calling console.dir');
51+
console.warn('calling console.warn');
52+
console.error('calling console.error');
53+
console.log(Promise.resolve('should not wait until resolved!'));
54+
}");
55+
56+
Assert.Equal(new[]
57+
{
58+
ConsoleType.TimeEnd,
59+
ConsoleType.Trace,
60+
ConsoleType.Dir,
61+
ConsoleType.Warning,
62+
ConsoleType.Error,
63+
ConsoleType.Log
64+
}, messages
65+
.Select(_ => _.Type)
66+
.ToArray());
67+
68+
Assert.Contains("calling console.time", messages[0].Text);
69+
70+
Assert.Equal(new[]
71+
{
72+
"calling console.trace",
73+
"calling console.dir",
74+
"calling console.warn",
75+
"calling console.error",
76+
"JSHandle@promise"
77+
}, messages
78+
.Skip(1)
79+
.Select(msg => msg.Text)
80+
.ToArray());
81+
}
82+
83+
[Fact]
84+
public async Task ShouldNotFailForWindowObject()
85+
{
86+
ConsoleMessage message = null;
87+
88+
void EventHandler(object sender, ConsoleEventArgs e)
89+
{
90+
message = e.Message;
91+
Page.Console -= EventHandler;
92+
}
93+
94+
Page.Console += EventHandler;
95+
96+
await Page.EvaluateExpressionAsync("console.error(window)");
97+
98+
Assert.Equal("JSHandle@object", message.Text);
99+
}
100+
}
101+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
3+
namespace PuppeteerSharp
4+
{
5+
public class ConsoleEventArgs : EventArgs
6+
{
7+
public ConsoleMessage Message { get; }
8+
9+
public ConsoleEventArgs(ConsoleMessage message) => Message = message;
10+
}
11+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Collections.Generic;
2+
3+
namespace PuppeteerSharp
4+
{
5+
public class ConsoleMessage
6+
{
7+
public ConsoleType Type { get; }
8+
public string Text { get; }
9+
public IList<JSHandle> Args { get; }
10+
11+
public ConsoleMessage(ConsoleType type, string text, IList<JSHandle> args)
12+
{
13+
Type = type;
14+
Text = text;
15+
Args = args;
16+
}
17+
}
18+
}

lib/PuppeteerSharp/ConsoleType.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace PuppeteerSharp
2+
{
3+
public enum ConsoleType
4+
{
5+
Log,
6+
Debug,
7+
Info,
8+
Error,
9+
Warning,
10+
Dir,
11+
Dirxml,
12+
Table,
13+
Trace,
14+
Clear,
15+
StartGroup,
16+
StartGroupCollapsed,
17+
EndGroup,
18+
Assert,
19+
Profile,
20+
ProfileEnd,
21+
Count,
22+
TimeEnd,
23+
}
24+
}

lib/PuppeteerSharp/Dialog.cs

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,20 @@
1-
using System;
2-
using System.Collections.Generic;
1+
using System.Collections.Generic;
32
using System.Threading.Tasks;
43

54
namespace PuppeteerSharp
65
{
76
public class Dialog
87
{
98
private Session _client;
9+
1010
public DialogType DialogType { get; set; }
1111
public string DefaultValue { get; set; }
1212
public string Message { get; set; }
13-
private static readonly Dictionary<string, DialogType> _dialogTypeMap = new Dictionary<string, DialogType>
14-
{
15-
["alert"] = DialogType.Alert,
16-
["prompt"] = DialogType.Prompt,
17-
["confirm"] = DialogType.Confirm,
18-
["beforeunload"] = DialogType.BeforeUnload
19-
};
2013

21-
public Dialog(Session client, string type, string message, string defaultValue)
14+
public Dialog(Session client, DialogType type, string message, string defaultValue)
2215
{
2316
_client = client;
24-
DialogType = GetDialogType(type);
17+
DialogType = type;
2518
Message = message;
2619
DefaultValue = defaultValue;
2720
}
@@ -42,15 +35,5 @@ public async Task Dismiss()
4235
{"accept", false}
4336
});
4437
}
45-
46-
public static DialogType GetDialogType(string dialogType)
47-
{
48-
if (_dialogTypeMap.ContainsKey(dialogType))
49-
{
50-
return _dialogTypeMap[dialogType];
51-
}
52-
53-
throw new PuppeteerException($"Unknown javascript dialog type {dialogType}");
54-
}
5538
}
5639
}

lib/PuppeteerSharp/DialogType.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
namespace PuppeteerSharp
1+
using System.Runtime.Serialization;
2+
3+
namespace PuppeteerSharp
24
{
35
public enum DialogType
46
{
57
Alert,
68
Prompt,
79
Confirm,
8-
BeforeUnload
10+
11+
[EnumMember(Value = "beforeunload")]
12+
BeforeUnload,
913
}
1014
}

lib/PuppeteerSharp/ExecutionContext.cs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,28 @@ public ExecutionContext(Session client, ContextPayload contextPayload, Func<dyna
2424
public string FrameId { get; internal set; }
2525
public bool IsDefault { get; internal set; }
2626

27-
public async Task<dynamic> EvaluateExpressionAsync(string script)
27+
public async Task<object> EvaluateExpressionAsync(string script)
28+
=> await EvaluateExpressionAsync<object>(script);
29+
30+
public async Task<T> EvaluateExpressionAsync<T>(string script)
2831
{
2932
var handle = await EvaluateExpressionHandleAsync(script);
30-
dynamic result = await handle.JsonValue();
33+
var result = await handle.JsonValue<T>();
3134
await handle.Dispose();
3235
return result;
3336
}
3437

35-
public async Task<T> EvaluateExpressionAsync<T>(string script)
36-
{
37-
var result = await EvaluateExpressionAsync(script);
38-
return ((JToken)result).ToObject<T>();
39-
}
38+
public async Task<object> EvaluateFunctionAsync(string script, params object[] args)
39+
=> await EvaluateFunctionAsync<object>(script, args);
4040

41-
public async Task<dynamic> EvaluateFunctionAsync(string script, params object[] args)
41+
public async Task<T> EvaluateFunctionAsync<T>(string script, params object[] args)
4242
{
4343
var handle = await EvaluateFunctionHandleAsync(script, args);
44-
dynamic result = await handle.JsonValue();
44+
var result = await handle.JsonValue<T>();
4545
await handle.Dispose();
4646
return result;
4747
}
4848

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-
5549
internal async Task<JSHandle> EvaluateExpressionHandleAsync(string script)
5650
{
5751
if (string.IsNullOrEmpty(script))

lib/PuppeteerSharp/FrameManager.cs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -104,23 +104,27 @@ private void OnExecutionContextDestroyed(int executionContextId)
104104
}
105105
}
106106

107-
private void OnExecutionContextCreated(ContextPayload contextPayload)
107+
public JSHandle CreateJsHandle(int contextId, dynamic remoteObject)
108108
{
109+
_contextIdToContext.TryGetValue(contextId, out var storedContext);
109110

110-
var context = new ExecutionContext(_client, contextPayload, (dynamic remoteObject) =>
111+
if (storedContext == null)
112+
{
113+
Console.WriteLine($"INTERNAL ERROR: missing context with id = {contextId}");
114+
}
115+
116+
if (remoteObject.subtype == "node")
111117
{
112-
_contextIdToContext.TryGetValue(contextPayload.Id, out var storedContext);
118+
return new ElementHandle(storedContext, _client, remoteObject, _page);
119+
}
120+
121+
return new JSHandle(storedContext, _client, remoteObject);
122+
}
113123

114-
if (storedContext == null)
115-
{
116-
Console.WriteLine($"INTERNAL ERROR: missing context with id = {contextPayload.Id}");
117-
}
118-
if (remoteObject.subtype == "node")
119-
{
120-
return new ElementHandle(storedContext, _client, remoteObject, _page);
121-
}
122-
return new JSHandle(storedContext, _client, remoteObject);
123-
});
124+
private void OnExecutionContextCreated(ContextPayload contextPayload)
125+
{
126+
var context = new ExecutionContext(_client, contextPayload,
127+
remoteObject => CreateJsHandle(contextPayload.Id, remoteObject));
124128

125129
_contextIdToContext[contextPayload.Id] = context;
126130

lib/PuppeteerSharp/Helper.cs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
using System;
22
using System.Threading.Tasks;
3+
using Newtonsoft.Json;
34

45
namespace PuppeteerSharp
56
{
67
internal class Helper
78
{
8-
internal static object ValueFromRemoteObject(dynamic remoteObject)
9+
internal static object ValueFromRemoteObject<T>(dynamic remoteObject)
910
{
1011
if (remoteObject.unserializableValue != null)
1112
{
@@ -19,7 +20,38 @@ internal static object ValueFromRemoteObject(dynamic remoteObject)
1920
throw new Exception("Unsupported unserializable value: " + remoteObject.unserializableValue);
2021
}
2122
}
22-
return remoteObject.value;
23+
24+
if (remoteObject.value == null)
25+
{
26+
return null;
27+
}
28+
29+
// https://chromedevtools.github.io/devtools-protocol/tot/Runtime#type-RemoteObject
30+
string objectValue = remoteObject.value.ToString();
31+
string objectType = remoteObject.type.ToString();
32+
33+
switch (objectType)
34+
{
35+
case "object":
36+
return JsonConvert.DeserializeObject<T>(objectValue);
37+
case "undefined":
38+
return null;
39+
case "number":
40+
switch (Type.GetTypeCode(typeof(T)))
41+
{
42+
case TypeCode.Int32:
43+
case TypeCode.Int64:
44+
return int.Parse(objectValue);
45+
default:
46+
return float.Parse(objectValue);
47+
}
48+
case "boolean":
49+
return bool.Parse(objectValue);
50+
case "bigint":
51+
return double.Parse(objectValue);
52+
default: // string, symbol, function
53+
return objectValue;
54+
}
2355
}
2456

2557
internal static async Task ReleaseObject(Session client, dynamic remoteObject)

0 commit comments

Comments
 (0)