Skip to content

Commit 6423c0b

Browse files
authored
chore(roll): roll Playwright to v1.48.0 (#3029)
1 parent 5b76172 commit 6423c0b

File tree

83 files changed

+2757
-841
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+2757
-841
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
| | Linux | macOS | Windows |
55
| :--- | :---: | :---: | :---: |
6-
| Chromium <!-- GEN:chromium-version -->129.0.6668.29<!-- GEN:stop --> ||||
6+
| Chromium <!-- GEN:chromium-version -->130.0.6723.31<!-- GEN:stop --> ||||
77
| WebKit <!-- GEN:webkit-version -->18.0<!-- GEN:stop --> ||||
8-
| Firefox <!-- GEN:firefox-version -->130.0<!-- GEN:stop --> ||||
8+
| Firefox <!-- GEN:firefox-version -->131.0<!-- GEN:stop --> ||||
99

1010
Playwright for .NET is the official language port of [Playwright](https://playwright.dev), the library to automate [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) with a single API. Playwright is built to enable cross-browser web automation that is **ever-green**, **capable**, **reliable** and **fast**.
1111

src/Common/Version.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<AssemblyVersion>1.47.0</AssemblyVersion>
44
<PackageVersion>$(AssemblyVersion)</PackageVersion>
5-
<DriverVersion>1.47.0-beta-1726138322000</DriverVersion>
5+
<DriverVersion>1.48.1</DriverVersion>
66
<ReleaseVersion>$(AssemblyVersion)</ReleaseVersion>
77
<FileVersion>$(AssemblyVersion)</FileVersion>
88
<NoDefaultExcludes>true</NoDefaultExcludes>

src/Playwright.Tests.TestServer/SimpleServer.cs

Lines changed: 24 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,16 @@ namespace Microsoft.Playwright.Tests.TestServer;
4949

5050
public class SimpleServer
5151
{
52-
const int MaxMessageSize = 256 * 1024;
53-
54-
private readonly IDictionary<string, Action<HttpContext>> _requestWaits;
55-
private readonly IList<Func<WebSocket, HttpContext, Task>> _waitForWebSocketConnectionRequestsWaits;
52+
private readonly IDictionary<string, Action<HttpContext>> _requestSubscribers;
53+
private readonly ConcurrentBag<Func<WebSocketWithEvents, HttpContext, Task>> _webSocketSubscribers;
5654
private readonly IDictionary<string, Func<HttpContext, Task>> _routes;
5755
private readonly IDictionary<string, (string username, string password)> _auths;
5856
private readonly IDictionary<string, string> _csp;
5957
private readonly IList<string> _gzipRoutes;
6058
private readonly string _contentRoot;
6159

62-
6360
private ArraySegment<byte> _onWebSocketConnectionData;
6461
private readonly IWebHost _webHost;
65-
private static int _counter;
66-
private readonly Dictionary<int, WebSocket> _clients = new();
6762

6863
public int Port { get; }
6964
public string Prefix { get; }
@@ -84,8 +79,8 @@ public SimpleServer(int port, string contentRoot, bool isHttps)
8479
EmptyPage = $"{Prefix}/empty.html";
8580

8681
var currentExecutionContext = TestExecutionContext.CurrentContext;
87-
_requestWaits = new ConcurrentDictionary<string, Action<HttpContext>>();
88-
_waitForWebSocketConnectionRequestsWaits = [];
82+
_requestSubscribers = new ConcurrentDictionary<string, Action<HttpContext>>();
83+
_webSocketSubscribers = [];
8984
_routes = new ConcurrentDictionary<string, Func<HttpContext, Task>>();
9085
_auths = new ConcurrentDictionary<string, (string username, string password)>();
9186
_csp = new ConcurrentDictionary<string, string>();
@@ -108,30 +103,23 @@ public SimpleServer(int port, string contentRoot, bool isHttps)
108103
var currentContext = typeof(TestExecutionContext).GetField("AsyncLocalCurrentContext", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null) as AsyncLocal<TestExecutionContext>;
109104
currentContext.Value = currentExecutionContext;
110105
}
111-
if (context.Request.Path == "/ws")
106+
if (context.WebSockets.IsWebSocketRequest && context.Request.Path == "/ws")
112107
{
113-
if (context.WebSockets.IsWebSocketRequest)
108+
var webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
109+
var testWebSocket = new WebSocketWithEvents(webSocket, context.Request);
110+
if (_onWebSocketConnectionData != null)
114111
{
115-
var webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
116-
foreach (var wait in _waitForWebSocketConnectionRequestsWaits)
117-
{
118-
_waitForWebSocketConnectionRequestsWaits.Remove(wait);
119-
await wait(webSocket, context).ConfigureAwait(false);
120-
}
121-
if (_onWebSocketConnectionData != null)
122-
{
123-
await webSocket.SendAsync(_onWebSocketConnectionData, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);
124-
}
125-
await ReceiveLoopAsync(webSocket, context.Request.Headers["User-Agent"].ToString().Contains("Firefox"), CancellationToken.None).ConfigureAwait(false);
112+
await webSocket.SendAsync(_onWebSocketConnectionData, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);
126113
}
127-
else if (!context.Response.HasStarted)
114+
foreach (var wait in _webSocketSubscribers)
128115
{
129-
context.Response.StatusCode = 400;
116+
await wait(testWebSocket, context).ConfigureAwait(false);
130117
}
118+
await testWebSocket.RunReceiveLoop().ConfigureAwait(false);
131119
return;
132120
}
133121

134-
if (_requestWaits.TryGetValue(context.Request.Path, out var requestWait))
122+
if (_requestSubscribers.TryGetValue(context.Request.Path, out var requestWait))
135123
{
136124
requestWait(context);
137125
}
@@ -264,9 +252,10 @@ public void Reset()
264252
_routes.Clear();
265253
_auths.Clear();
266254
_csp.Clear();
267-
_requestWaits.Clear();
255+
_requestSubscribers.Clear();
268256
_gzipRoutes.Clear();
269257
_onWebSocketConnectionData = null;
258+
_webSocketSubscribers.Clear();
270259
}
271260

272261
public void EnableGzip(string path) => _gzipRoutes.Add(path);
@@ -290,33 +279,33 @@ public void SetRedirect(string from, string to) => SetRoute(from, context =>
290279
public async Task<T> WaitForRequest<T>(string path, Func<HttpRequest, T> selector)
291280
{
292281
var taskCompletion = new TaskCompletionSource<T>();
293-
_requestWaits.Add(path, context =>
282+
_requestSubscribers.Add(path, context =>
294283
{
295284
taskCompletion.SetResult(selector(context.Request));
296285
});
297286

298287
var request = await taskCompletion.Task.ConfigureAwait(false);
299-
_requestWaits.Remove(path);
288+
_requestSubscribers.Remove(path);
300289

301290
return request;
302291
}
303292

304293
public Task WaitForRequest(string path) => WaitForRequest(path, _ => true);
305294

306-
public async Task<(WebSocket, HttpRequest)> WaitForWebSocketConnectionRequest()
295+
public Task<WebSocketWithEvents> WaitForWebSocketAsync()
307296
{
308-
var taskCompletion = new TaskCompletionSource<(WebSocket, HttpRequest)>();
309-
OnceWebSocketConnection((WebSocket ws, HttpContext context) =>
297+
var tcs = new TaskCompletionSource<WebSocketWithEvents>();
298+
OnceWebSocketConnection((ws, _) =>
310299
{
311-
taskCompletion.SetResult((ws, context.Request));
300+
tcs.SetResult(ws);
312301
return Task.CompletedTask;
313302
});
314-
return await taskCompletion.Task.ConfigureAwait(false);
303+
return tcs.Task;
315304
}
316305

317-
public void OnceWebSocketConnection(Func<WebSocket, HttpContext, Task> handler)
306+
public void OnceWebSocketConnection(Func<WebSocketWithEvents, HttpContext, Task> handler)
318307
{
319-
_waitForWebSocketConnectionRequestsWaits.Add(handler);
308+
_webSocketSubscribers.Add(handler);
320309
}
321310

322311
private static bool Authenticate(string username, string password, HttpContext context)
@@ -332,73 +321,4 @@ private static bool Authenticate(string username, string password, HttpContext c
332321
}
333322
return false;
334323
}
335-
336-
private async Task ReceiveLoopAsync(WebSocket webSocket, bool sendCloseMessage, CancellationToken token)
337-
{
338-
int connectionId = NextConnectionId();
339-
_clients.Add(connectionId, webSocket);
340-
341-
byte[] buffer = new byte[MaxMessageSize];
342-
343-
try
344-
{
345-
while (true)
346-
{
347-
var result = await webSocket.ReceiveAsync(new(buffer), token).ConfigureAwait(false);
348-
349-
if (result.MessageType == WebSocketMessageType.Close)
350-
{
351-
if (sendCloseMessage)
352-
{
353-
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Close", CancellationToken.None).ConfigureAwait(false);
354-
}
355-
break;
356-
}
357-
358-
var data = await ReadFrames(result, webSocket, buffer, token).ConfigureAwait(false);
359-
360-
if (data.Count == 0)
361-
{
362-
break;
363-
}
364-
}
365-
}
366-
finally
367-
{
368-
_clients.Remove(connectionId);
369-
}
370-
}
371-
372-
private async Task<ArraySegment<byte>> ReadFrames(WebSocketReceiveResult result, WebSocket webSocket, byte[] buffer, CancellationToken token)
373-
{
374-
int count = result.Count;
375-
376-
while (!result.EndOfMessage)
377-
{
378-
if (count >= MaxMessageSize)
379-
{
380-
string closeMessage = $"Maximum message size: {MaxMessageSize} bytes.";
381-
await webSocket.CloseAsync(WebSocketCloseStatus.MessageTooBig, closeMessage, token).ConfigureAwait(false);
382-
return new();
383-
}
384-
385-
result = await webSocket.ReceiveAsync(new(buffer, count, MaxMessageSize - count), token).ConfigureAwait(false);
386-
count += result.Count;
387-
388-
}
389-
return new(buffer, 0, count);
390-
}
391-
392-
393-
private static int NextConnectionId()
394-
{
395-
int id = Interlocked.Increment(ref _counter);
396-
397-
if (id == int.MaxValue)
398-
{
399-
throw new("connection id limit reached: " + id);
400-
}
401-
402-
return id;
403-
}
404324
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) Microsoft Corporation.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
using System;
26+
using System.Net.WebSockets;
27+
using System.Text;
28+
using System.Threading;
29+
using System.Threading.Tasks;
30+
using Microsoft.AspNetCore.Http;
31+
32+
namespace Microsoft.Playwright.Tests.TestServer;
33+
34+
public class WebSocketWithEvents : IDisposable
35+
{
36+
public readonly WebSocket ws;
37+
public readonly HttpRequest request;
38+
private readonly CancellationTokenSource _cts = new();
39+
40+
public WebSocketWithEvents(WebSocket ws, HttpRequest request)
41+
{
42+
this.ws = ws;
43+
this.request = request;
44+
}
45+
46+
public event EventHandler<string> MessageReceived;
47+
public event EventHandler<(WebSocketCloseStatus? Code, string Reason)> Closed;
48+
49+
internal async Task RunReceiveLoop()
50+
{
51+
var buffer = new byte[1024 * 4];
52+
try
53+
{
54+
while (ws.State == WebSocketState.Open && !_cts.IsCancellationRequested)
55+
{
56+
var result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), _cts.Token).ConfigureAwait(false);
57+
if (result.MessageType == WebSocketMessageType.Text)
58+
{
59+
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
60+
MessageReceived?.Invoke(this, message);
61+
}
62+
else if (result.MessageType == WebSocketMessageType.Close)
63+
{
64+
await CloseAsync().ConfigureAwait(false);
65+
break;
66+
}
67+
}
68+
}
69+
catch (OperationCanceledException)
70+
{
71+
// Normal cancellation, do nothing
72+
}
73+
finally
74+
{
75+
if (ws.State == WebSocketState.CloseReceived)
76+
{
77+
Closed?.Invoke(this, (ws.CloseStatus, ws.CloseStatusDescription));
78+
}
79+
await CloseAsync().ConfigureAwait(false);
80+
}
81+
}
82+
83+
public async Task SendAsync(string message)
84+
{
85+
if (ws.State != WebSocketState.Open)
86+
throw new InvalidOperationException("WebSocket is not open.");
87+
88+
var buffer = Encoding.UTF8.GetBytes(message);
89+
await ws.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, _cts.Token).ConfigureAwait(false);
90+
}
91+
92+
public async Task CloseAsync(WebSocketCloseStatus closeStatus = WebSocketCloseStatus.NormalClosure, string statusDescription = "Closing")
93+
{
94+
if (ws.State == WebSocketState.Open)
95+
{
96+
try
97+
{
98+
await ws.CloseAsync(closeStatus, statusDescription, CancellationToken.None).ConfigureAwait(false);
99+
}
100+
catch (WebSocketException)
101+
{
102+
// The WebSocket might have been closed by the server, ignore the exception
103+
}
104+
}
105+
}
106+
107+
public void Dispose()
108+
{
109+
_cts.Cancel();
110+
_cts.Dispose();
111+
ws.Dispose();
112+
}
113+
}

src/Playwright.Tests/BrowserTypeConnectOverCDPTests.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,16 @@ public async Task ShouldConnectToAnExistingCDPSession()
5555
[Skip(SkipAttribute.Targets.Firefox, SkipAttribute.Targets.Webkit)]
5656
public async Task ShouldSendExtraHeadersWithConnectRequest()
5757
{
58-
var waitForRequest = Server.WaitForWebSocketConnectionRequest();
58+
var webSocketTask = Server.WaitForWebSocketAsync();
5959
BrowserType.ConnectOverCDPAsync($"ws://127.0.0.1:{Server.Port}/ws", new()
6060
{
6161
Headers = new Dictionary<string, string> {
62-
{ "x-foo-bar", "fookek" }
63-
},
62+
{ "x-foo-bar", "fookek" }
63+
},
6464
}).IgnoreException();
65-
(_, var req) = await waitForRequest;
66-
Assert.AreEqual("fookek", req.Headers["x-foo-bar"]);
67-
StringAssert.Contains("Playwright", req.Headers["user-agent"]);
65+
var webSocket = await webSocketTask;
66+
Assert.AreEqual("fookek", webSocket.request.Headers["x-foo-bar"]);
67+
StringAssert.Contains("Playwright", webSocket.request.Headers["user-agent"]);
6868
}
6969

7070
[PlaywrightTest("chromium/chromium.spec.ts", "should report all pages in an existing browser")]
@@ -102,8 +102,8 @@ public async Task ShouldPrintCustomWsCloseError()
102102
{
103103
Server.OnceWebSocketConnection(async (ws, request) =>
104104
{
105-
await ws.ReceiveAsync(new byte[1024], CancellationToken.None);
106-
await ws.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "Oh my!", CancellationToken.None);
105+
await ws.ws.ReceiveAsync(new byte[1024], CancellationToken.None);
106+
await ws.ws.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "Oh my!", CancellationToken.None);
107107
});
108108
var error = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(() => BrowserType.ConnectOverCDPAsync($"ws://localhost:{Server.Port}/ws"));
109109
StringAssert.Contains("Browser logs:\n\nOh my!\n", error.Message);

0 commit comments

Comments
 (0)