Skip to content

Commit db30779

Browse files
Meir017kblok
authored andcommitted
Implemented Network Events tests (#169)
1 parent 52b6f06 commit db30779

File tree

9 files changed

+440
-103
lines changed

9 files changed

+440
-103
lines changed

appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: 1.0.{build}
22
branches:
33
only:
44
- master
5-
image: Previous Visual Studio 2017
5+
image: Visual Studio 2017
66
configuration: Release
77
before_build:
88
- ps: >-
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Newtonsoft.Json.Linq;
3+
using System.Collections.Generic;
4+
using System.Net;
5+
using System.Net.Http;
6+
using System.Threading.Tasks;
7+
using Xunit;
8+
9+
namespace PuppeteerSharp.Tests.Network
10+
{
11+
[Collection("PuppeteerLoaderFixture collection")]
12+
public class NetworkEventTests : PuppeteerPageBaseTest
13+
{
14+
[Fact]
15+
public async Task PageEventsRequest()
16+
{
17+
var requests = new List<Request>();
18+
Page.RequestCreated += (sender, e) => requests.Add(e.Request);
19+
await Page.GoToAsync(TestConstants.EmptyPage);
20+
Assert.Single(requests);
21+
Assert.Equal(TestConstants.EmptyPage, requests[0].Url);
22+
Assert.Equal(ResourceType.Document, requests[0].ResourceType);
23+
Assert.Equal(HttpMethod.Get, requests[0].Method);
24+
Assert.NotNull(requests[0].Response);
25+
Assert.Equal(Page.MainFrame, requests[0].Frame);
26+
Assert.Equal(TestConstants.EmptyPage, requests[0].Frame.Url);
27+
}
28+
29+
[Fact]
30+
public async Task PageEventsRequestShouldReportPostData()
31+
{
32+
await Page.GoToAsync(TestConstants.EmptyPage);
33+
Server.SetRoute("/post", context => Task.CompletedTask);
34+
Request request = null;
35+
Page.RequestCreated += (sender, e) => request = e.Request;
36+
await Page.EvaluateExpressionHandleAsync("fetch('./post', { method: 'POST', body: JSON.stringify({ foo: 'bar'})})");
37+
Assert.NotNull(request);
38+
Assert.Equal("{\"foo\":\"bar\"}", request.PostData);
39+
}
40+
41+
[Fact]
42+
public async Task PageEventsResponse()
43+
{
44+
var responses = new List<Response>();
45+
Page.ResponseCreated += (sender, e) => responses.Add(e.Response);
46+
await Page.GoToAsync(TestConstants.EmptyPage);
47+
Assert.Single(responses);
48+
Assert.Equal(TestConstants.EmptyPage, responses[0].Url);
49+
Assert.Equal(HttpStatusCode.OK, responses[0].Status);
50+
Assert.NotNull(responses[0].Request);
51+
}
52+
53+
[Fact]
54+
public async Task PageEventsResponseShouldProvideBody()
55+
{
56+
Response response = null;
57+
Page.ResponseCreated += (sender, e) => response = e.Response;
58+
await Page.GoToAsync(TestConstants.ServerUrl + "/simple.json");
59+
Assert.NotNull(response);
60+
var responseText = await new HttpClient().GetStringAsync(TestConstants.ServerUrl + "/simple.json");
61+
Assert.Equal(responseText, await response.TextAsync());
62+
Assert.Equal(JObject.Parse(responseText), await response.JsonAsync());
63+
}
64+
65+
[Fact]
66+
public async Task PageEventsResponseShouldNotReportBodyUnlessRequestIsFinished()
67+
{
68+
await Page.GoToAsync(TestConstants.EmptyPage);
69+
// Setup server to trap request.
70+
var serverResponseCompletion = new TaskCompletionSource<bool>();
71+
HttpResponse serverResponse = null;
72+
Server.SetRoute("/get", context =>
73+
{
74+
serverResponse = context.Response;
75+
context.Response.WriteAsync("hello ");
76+
return serverResponseCompletion.Task;
77+
});
78+
// Setup page to trap response.
79+
Response pageResponse = null;
80+
var requestFinished = false;
81+
Page.ResponseCreated += (sender, e) => pageResponse = e.Response;
82+
Page.RequestFinished += (sender, e) => requestFinished = true;
83+
// send request and wait for server response
84+
Task WaitForPageResponseEvent()
85+
{
86+
var completion = new TaskCompletionSource<bool>();
87+
Page.ResponseCreated += (sender, e) => completion.SetResult(true);
88+
return completion.Task;
89+
}
90+
await Task.WhenAll(
91+
Page.EvaluateExpressionAsync("fetch('/get', { method: 'GET'})"),
92+
WaitForPageResponseEvent()
93+
);
94+
95+
Assert.NotNull(serverResponse);
96+
Assert.NotNull(pageResponse);
97+
Assert.Equal(HttpStatusCode.OK, pageResponse.Status);
98+
Assert.False(requestFinished);
99+
100+
var responseText = pageResponse.TextAsync();
101+
// Write part of the response and wait for it to be flushed.
102+
await serverResponse.WriteAsync("wor");
103+
// Finish response.
104+
await serverResponse.WriteAsync("ld!");
105+
serverResponseCompletion.SetResult(true);
106+
Assert.Equal("hello world!", await responseText);
107+
}
108+
109+
[Fact]
110+
public async Task PageEventsRequestFailed()
111+
{
112+
await Page.SetRequestInterceptionAsync(true);
113+
Page.RequestCreated += async (sender, e) =>
114+
{
115+
if (e.Request.Url.EndsWith("css"))
116+
await e.Request.AbortAsync();
117+
else
118+
await e.Request.ContinueAsync();
119+
};
120+
var failedRequests = new List<Request>();
121+
Page.RequestFailed += (sender, e) => failedRequests.Add(e.Request);
122+
await Page.GoToAsync(TestConstants.ServerUrl + "/one-style.html");
123+
124+
Assert.Single(failedRequests);
125+
Assert.Contains("one-style.css", failedRequests[0].Url);
126+
Assert.Null(failedRequests[0].Response);
127+
Assert.Equal(ResourceType.StyleSheet, failedRequests[0].ResourceType);
128+
Assert.Equal("net::ERR_FAILED", failedRequests[0].Failure);
129+
Assert.NotNull(failedRequests[0].Frame);
130+
}
131+
132+
[Fact]
133+
public async Task PageEventsRequestFinished()
134+
{
135+
var requests = new List<Request>();
136+
Page.RequestFinished += (sender, e) => requests.Add(e.Request);
137+
await Page.GoToAsync(TestConstants.EmptyPage);
138+
Assert.Single(requests);
139+
Assert.Equal(TestConstants.EmptyPage, requests[0].Url);
140+
Assert.NotNull(requests[0].Response);
141+
Assert.Equal(HttpMethod.Get, requests[0].Method);
142+
Assert.Equal(Page.MainFrame, requests[0].Frame);
143+
Assert.Equal(TestConstants.EmptyPage, requests[0].Frame.Url);
144+
}
145+
146+
[Fact]
147+
public async Task ShouldFireEventsInProperOrder()
148+
{
149+
var events = new List<string>();
150+
Page.RequestCreated += (sender, e) => events.Add("request");
151+
Page.ResponseCreated += (sender, e) => events.Add("response");
152+
Page.RequestFinished += (sender, e) => events.Add("requestfinished");
153+
await Page.GoToAsync(TestConstants.EmptyPage);
154+
Assert.Equal(new[] { "request", "response", "requestfinished" }, events);
155+
}
156+
157+
[Fact]
158+
public async Task ShouldSupportRedirects()
159+
{
160+
var events = new List<string>();
161+
Page.RequestCreated += (sender, e) => events.Add($"{e.Request.Method} {e.Request.Url}");
162+
Page.ResponseCreated += (sender, e) => events.Add($"{(int)e.Response.Status} {e.Response.Url}");
163+
Page.RequestFinished += (sender, e) => events.Add($"DONE {e.Request.Url}");
164+
Page.RequestFailed += (sender, e) => events.Add($"FAIL {e.Request.Url}");
165+
Server.SetRedirect("/foo.html", "/empty.html");
166+
const string FOO_URL = TestConstants.ServerUrl + "/foo.html";
167+
await Page.GoToAsync(FOO_URL);
168+
Assert.Equal(new[] {
169+
$"GET {FOO_URL}",
170+
$"302 {FOO_URL}",
171+
$"DONE {FOO_URL}",
172+
$"GET {TestConstants.EmptyPage}",
173+
$"200 {TestConstants.EmptyPage}",
174+
$"DONE {TestConstants.EmptyPage}"
175+
}, events);
176+
}
177+
}
178+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Net.Http;
3+
using Newtonsoft.Json;
4+
5+
namespace PuppeteerSharp
6+
{
7+
internal class HttpMethodConverter : JsonConverter
8+
{
9+
public override bool CanConvert(Type objectType) => objectType == typeof(string);
10+
11+
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
12+
{
13+
return new HttpMethod((string)reader.Value);
14+
}
15+
16+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
17+
{
18+
var httpMethod = (HttpMethod)value;
19+
serializer.Serialize(writer, httpMethod.Method);
20+
}
21+
}
22+
}

lib/PuppeteerSharp/MultiMap.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace PuppeteerSharp
8+
{
9+
internal class MultiMap<TKey, TValue>
10+
{
11+
private readonly Dictionary<TKey, HashSet<TValue>> _map = new Dictionary<TKey, HashSet<TValue>>();
12+
13+
public void Add(TKey key, TValue value)
14+
{
15+
if (_map.TryGetValue(key, out var set))
16+
{
17+
set.Add(value);
18+
}
19+
else
20+
{
21+
set = new HashSet<TValue> { value };
22+
_map.Add(key, set);
23+
}
24+
}
25+
26+
public HashSet<TValue> Get(TKey key)
27+
=> _map.TryGetValue(key, out var set) ? set : new HashSet<TValue>();
28+
29+
public bool Has(TKey key, TValue value)
30+
=> _map.TryGetValue(key, out var set) && set.Contains(value);
31+
32+
public bool Delete(TKey key, TValue value)
33+
=> _map.TryGetValue(key, out var set) && set.Remove(value);
34+
35+
public TValue FirstValue(TKey key)
36+
=> _map.TryGetValue(key, out var set) ? set.FirstOrDefault() : default(TValue);
37+
}
38+
}

lib/PuppeteerSharp/NetworkManager.cs

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ public class NetworkManager
2121
private bool _userRequestInterceptionEnabled;
2222
private bool _protocolRequestInterceptionEnabled;
2323

24-
private List<KeyValuePair<string, string>> _requestHashToRequestIds = new List<KeyValuePair<string, string>>();
25-
private List<KeyValuePair<string, string>> _requestHashToInterceptionIds = new List<KeyValuePair<string, string>>();
24+
private MultiMap<string, string> _requestHashToRequestIds = new MultiMap<string, string>();
25+
private MultiMap<string, string> _requestHashToInterceptionIds = new MultiMap<string, string>();
2626
private FrameManager _frameManager;
2727
#endregion
2828

@@ -40,11 +40,9 @@ public NetworkManager(Session client, FrameManager frameManager)
4040
public event EventHandler<RequestEventArgs> RequestCreated;
4141
public event EventHandler<RequestEventArgs> RequestFinished;
4242
public event EventHandler<RequestEventArgs> RequestFailed;
43-
public event EventHandler<ResponseReceivedArgs> ResponseReceivedFinished;
4443

4544
#endregion
4645

47-
4846
#region Public Methods
4947

5048
public async Task AuthenticateAsync(Credentials credentials)
@@ -131,10 +129,9 @@ private void OnLoadingFailed(MessageEventArgs e)
131129
{
132130
// For certain requestIds we never receive requestWillBeSent event.
133131
// @see https://crbug.com/750469
134-
if (_requestIdToRequest.ContainsKey(e.MessageData.requestId.ToString()))
132+
string requestId = e.MessageData.requestId.ToString();
133+
if (_requestIdToRequest.TryGetValue(requestId, out var request))
135134
{
136-
var request = _requestIdToRequest[e.MessageData.requestId.ToString()];
137-
138135
request.Failure = e.MessageData.errorText.ToString();
139136
request.CompleteTaskWrapper.SetResult(true);
140137
_requestIdToRequest.Remove(request.RequestId);
@@ -155,10 +152,9 @@ private void OnLoadingFinished(MessageEventArgs e)
155152
{
156153
// For certain requestIds we never receive requestWillBeSent event.
157154
// @see https://crbug.com/750469
158-
if (_requestIdToRequest.ContainsKey(e.MessageData.requestId.ToString()))
155+
string requestId = e.MessageData.requestId.ToString();
156+
if (_requestIdToRequest.TryGetValue(requestId, out var request))
159157
{
160-
var request = _requestIdToRequest[e.MessageData.requestId.ToString()];
161-
162158
request.CompleteTaskWrapper.SetResult(true);
163159
_requestIdToRequest.Remove(request.RequestId);
164160

@@ -178,9 +174,9 @@ private void OnLoadingFinished(MessageEventArgs e)
178174
private void OnResponseReceived(MessageEventArgs e)
179175
{
180176
// FileUpload sends a response without a matching request.
181-
if (_requestIdToRequest.ContainsKey(e.MessageData.requestId.ToString()))
177+
string requestId = e.MessageData.requestId.ToString();
178+
if (_requestIdToRequest.TryGetValue(requestId, out var request))
182179
{
183-
var request = _requestIdToRequest[e.MessageData.requestId.ToString()];
184180
var response = new Response(
185181
_client,
186182
request,
@@ -241,18 +237,16 @@ private async Task OnRequestInterceptedAsync(MessageEventArgs e)
241237
return;
242238
}
243239

244-
var requestHash = e.MessageData.request.ToObject<Payload>().Hash;
245-
246-
if (_requestHashToRequestIds.Any(i => i.Key == requestHash))
240+
string requestHash = e.MessageData.request.ToObject<Payload>().Hash;
241+
var requestId = _requestHashToRequestIds.FirstValue(requestHash);
242+
if (requestId != null)
247243
{
248-
var item = _requestHashToRequestIds.FirstOrDefault(i => i.Key == requestHash);
249-
var requestId = item.Value;
250-
_requestHashToRequestIds.Remove(item);
244+
_requestHashToRequestIds.Delete(requestHash, requestId);
251245
HandleRequestStart(requestId, e.MessageData);
252246
}
253247
else
254248
{
255-
_requestHashToInterceptionIds.Add(new KeyValuePair<string, string>(requestHash, e.MessageData.interceptionId.ToString()));
249+
_requestHashToInterceptionIds.Add(requestHash, e.MessageData.interceptionId.ToString());
256250
HandleRequestStart(null, e.MessageData);
257251
}
258252
}
@@ -323,31 +317,23 @@ private void OnRequestWillBeSent(MessageEventArgs e)
323317
if (_protocolRequestInterceptionEnabled)
324318
{
325319
// All redirects are handled in requestIntercepted.
326-
if (e.MessageData.redirectResponse == null)
320+
if (e.MessageData.redirectResponse != null)
327321
{
328-
var requestHash = e.MessageData.request.ToObject<Payload>().Hash;
329-
330-
KeyValuePair<string, string>? interceptionItem = null;
331-
332-
if (_requestHashToInterceptionIds.Any(i => i.Key == requestHash))
333-
{
334-
interceptionItem = _requestHashToInterceptionIds.First(i => i.Key == requestHash);
335-
}
336-
337-
if (interceptionItem.HasValue && _interceptionIdToRequest.ContainsKey(interceptionItem.Value.Value))
338-
{
339-
var request = _interceptionIdToRequest[interceptionItem.Value.Value];
340-
341-
request.RequestId = e.MessageData.requestId;
342-
_requestIdToRequest[e.MessageData.requestId.ToString()] = request;
343-
_requestHashToInterceptionIds.Remove(interceptionItem.Value);
344-
}
345-
else
346-
{
347-
_requestHashToRequestIds.Add(new KeyValuePair<string, string>(requestHash, e.MessageData.requestId.ToString()));
348-
}
349322
return;
350323
}
324+
string requestHash = e.MessageData.request.ToObject<Payload>().Hash;
325+
var interceptionId = _requestHashToInterceptionIds.FirstValue(requestHash);
326+
if (interceptionId != null && _interceptionIdToRequest.TryGetValue(interceptionId, out var request))
327+
{
328+
request.RequestId = e.MessageData.requestId.ToString();
329+
_requestIdToRequest[e.MessageData.requestId.ToString()] = request;
330+
_requestHashToInterceptionIds.Delete(requestHash, interceptionId);
331+
}
332+
else
333+
{
334+
_requestHashToRequestIds.Add(requestHash, e.MessageData.requestId.ToString());
335+
}
336+
return;
351337
}
352338

353339
if (e.MessageData.redirectResponse != null && _requestIdToRequest.ContainsKey(e.MessageData.requestId.ToString()))

0 commit comments

Comments
 (0)