Skip to content

Commit

Permalink
chore(roll): roll Playwright to v1.48.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mxschmitt committed Oct 21, 2024
1 parent 4ed6b39 commit ed71bd3
Show file tree
Hide file tree
Showing 82 changed files with 2,749 additions and 819 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->129.0.6668.29<!-- GEN:stop --> ||||
| Chromium <!-- GEN:chromium-version -->130.0.6723.31<!-- GEN:stop --> ||||
| WebKit <!-- GEN:webkit-version -->18.0<!-- GEN:stop --> ||||
| Firefox <!-- GEN:firefox-version -->130.0<!-- GEN:stop --> ||||
| Firefox <!-- GEN:firefox-version -->131.0<!-- GEN:stop --> ||||

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**.

Expand Down
2 changes: 1 addition & 1 deletion src/Common/Version.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyVersion>1.47.0</AssemblyVersion>
<PackageVersion>$(AssemblyVersion)</PackageVersion>
<DriverVersion>1.47.0-beta-1726138322000</DriverVersion>
<DriverVersion>1.48.0</DriverVersion>
<ReleaseVersion>$(AssemblyVersion)</ReleaseVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<NoDefaultExcludes>true</NoDefaultExcludes>
Expand Down
123 changes: 30 additions & 93 deletions src/Playwright.Tests.TestServer/SimpleServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,16 @@ namespace Microsoft.Playwright.Tests.TestServer;

public class SimpleServer
{
const int MaxMessageSize = 256 * 1024;

private readonly IDictionary<string, Action<HttpContext>> _requestWaits;
private readonly IList<Func<WebSocket, HttpContext, Task>> _waitForWebSocketConnectionRequestsWaits;
private readonly ConcurrentBag<Func<WebSocketWithEvents, HttpContext, Task>> _waitForWebSocketConnectionRequestsWaits;
private readonly IDictionary<string, Func<HttpContext, Task>> _routes;
private readonly IDictionary<string, (string username, string password)> _auths;
private readonly IDictionary<string, string> _csp;
private readonly IList<string> _gzipRoutes;
private readonly string _contentRoot;


private ArraySegment<byte> _onWebSocketConnectionData;
private readonly IWebHost _webHost;
private static int _counter;
private readonly Dictionary<int, WebSocket> _clients = new();

public int Port { get; }
public string Prefix { get; }
Expand Down Expand Up @@ -92,6 +87,13 @@ public SimpleServer(int port, string contentRoot, bool isHttps)
_gzipRoutes = new List<string>();
_contentRoot = contentRoot;

var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddConsole()
.SetMinimumLevel(LogLevel.Debug); // or LogLevel.Debug
});

_webHost = new WebHostBuilder()
.ConfigureLogging(logging =>
{
Expand All @@ -108,26 +110,19 @@ public SimpleServer(int port, string contentRoot, bool isHttps)
var currentContext = typeof(TestExecutionContext).GetField("AsyncLocalCurrentContext", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null) as AsyncLocal<TestExecutionContext>;
currentContext.Value = currentExecutionContext;
}
if (context.Request.Path == "/ws")
if (context.WebSockets.IsWebSocketRequest && context.Request.Path == "/ws")
{
if (context.WebSockets.IsWebSocketRequest)
var webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
var advancedWebSocket = new WebSocketWithEvents(webSocket);
if (_onWebSocketConnectionData != null)
{
var webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
foreach (var wait in _waitForWebSocketConnectionRequestsWaits)
{
_waitForWebSocketConnectionRequestsWaits.Remove(wait);
await wait(webSocket, context).ConfigureAwait(false);
}
if (_onWebSocketConnectionData != null)
{
await webSocket.SendAsync(_onWebSocketConnectionData, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);
}
await ReceiveLoopAsync(webSocket, context.Request.Headers["User-Agent"].ToString().Contains("Firefox"), CancellationToken.None).ConfigureAwait(false);
await webSocket.SendAsync(_onWebSocketConnectionData, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);
}
else if (!context.Response.HasStarted)
foreach (var wait in _waitForWebSocketConnectionRequestsWaits)
{
context.Response.StatusCode = 400;
await wait(advancedWebSocket, context).ConfigureAwait(false);
}
await advancedWebSocket.RunReceiveLoop().ConfigureAwait(false);
return;
}

Expand Down Expand Up @@ -267,6 +262,7 @@ public void Reset()
_requestWaits.Clear();
_gzipRoutes.Clear();
_onWebSocketConnectionData = null;
_waitForWebSocketConnectionRequestsWaits.Clear();
}

public void EnableGzip(string path) => _gzipRoutes.Add(path);
Expand Down Expand Up @@ -303,18 +299,28 @@ public async Task<T> WaitForRequest<T>(string path, Func<HttpRequest, T> selecto

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

public Task<WebSocketWithEvents> WaitForWebSocketAsync()
{
var tcs = new TaskCompletionSource<WebSocketWithEvents>();
OnceWebSocketConnection((ws, _) => {
tcs.SetResult(ws);
return Task.CompletedTask;
});
return tcs.Task;
}

public async Task<(WebSocket, HttpRequest)> WaitForWebSocketConnectionRequest()
{
var taskCompletion = new TaskCompletionSource<(WebSocket, HttpRequest)>();
OnceWebSocketConnection((WebSocket ws, HttpContext context) =>
OnceWebSocketConnection((WebSocketWithEvents ws, HttpContext context) =>
{
taskCompletion.SetResult((ws, context.Request));
taskCompletion.SetResult((ws._ws, context.Request));
return Task.CompletedTask;
});
return await taskCompletion.Task.ConfigureAwait(false);
}

public void OnceWebSocketConnection(Func<WebSocket, HttpContext, Task> handler)
public void OnceWebSocketConnection(Func<WebSocketWithEvents, HttpContext, Task> handler)
{
_waitForWebSocketConnectionRequestsWaits.Add(handler);
}
Expand All @@ -332,73 +338,4 @@ private static bool Authenticate(string username, string password, HttpContext c
}
return false;
}

private async Task ReceiveLoopAsync(WebSocket webSocket, bool sendCloseMessage, CancellationToken token)
{
int connectionId = NextConnectionId();
_clients.Add(connectionId, webSocket);

byte[] buffer = new byte[MaxMessageSize];

try
{
while (true)
{
var result = await webSocket.ReceiveAsync(new(buffer), token).ConfigureAwait(false);

if (result.MessageType == WebSocketMessageType.Close)
{
if (sendCloseMessage)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Close", CancellationToken.None).ConfigureAwait(false);
}
break;
}

var data = await ReadFrames(result, webSocket, buffer, token).ConfigureAwait(false);

if (data.Count == 0)
{
break;
}
}
}
finally
{
_clients.Remove(connectionId);
}
}

private async Task<ArraySegment<byte>> ReadFrames(WebSocketReceiveResult result, WebSocket webSocket, byte[] buffer, CancellationToken token)
{
int count = result.Count;

while (!result.EndOfMessage)
{
if (count >= MaxMessageSize)
{
string closeMessage = $"Maximum message size: {MaxMessageSize} bytes.";
await webSocket.CloseAsync(WebSocketCloseStatus.MessageTooBig, closeMessage, token).ConfigureAwait(false);
return new();
}

result = await webSocket.ReceiveAsync(new(buffer, count, MaxMessageSize - count), token).ConfigureAwait(false);
count += result.Count;

}
return new(buffer, 0, count);
}


private static int NextConnectionId()
{
int id = Interlocked.Increment(ref _counter);

if (id == int.MaxValue)
{
throw new("connection id limit reached: " + id);
}

return id;
}
}
112 changes: 112 additions & 0 deletions src/Playwright.Tests.TestServer/WebSocketWithEvents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* MIT License
*
* Copyright (c) Microsoft Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Playwright.Tests.TestServer;

public class WebSocketWithEvents : IDisposable
{
public readonly WebSocket _ws;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();

public WebSocketWithEvents(WebSocket webSocket)
{
_ws = webSocket;
}

public event EventHandler<string> MessageReceived;
public event EventHandler<(WebSocketCloseStatus? Code, string Reason)> Closed;

internal async Task RunReceiveLoop()
{
var buffer = new byte[1024 * 4];
try
{
while (_ws.State == WebSocketState.Open && !_cts.IsCancellationRequested)
{
var result = await _ws.ReceiveAsync(new ArraySegment<byte>(buffer), _cts.Token).ConfigureAwait(false);
Console.WriteLine($"Received {result.Count} bytes messagetype: {result.MessageType}");
if (result.MessageType == WebSocketMessageType.Text)
{
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
MessageReceived?.Invoke(this, message);
}
else if (result.MessageType == WebSocketMessageType.Close)
{
await CloseAsync().ConfigureAwait(false);
break;
}
}
}
catch (OperationCanceledException)
{
// Normal cancellation, do nothing
}
finally
{
if (_ws.State == WebSocketState.CloseReceived)
{
Closed?.Invoke(this, (_ws.CloseStatus, _ws.CloseStatusDescription));
}
await CloseAsync().ConfigureAwait(false);
}
}

public async Task SendAsync(string message)
{
if (_ws.State != WebSocketState.Open)
throw new InvalidOperationException("WebSocket is not open.");

var buffer = Encoding.UTF8.GetBytes(message);
await _ws.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, _cts.Token).ConfigureAwait(false);
}

public async Task CloseAsync(WebSocketCloseStatus closeStatus = WebSocketCloseStatus.NormalClosure, string statusDescription = "Closing")
{
Console.WriteLine(Environment.StackTrace);
if (_ws.State == WebSocketState.Open)
{
try
{
await _ws.CloseAsync(closeStatus, statusDescription, CancellationToken.None).ConfigureAwait(false);
}
catch (WebSocketException)
{
// The WebSocket might have been closed by the server, ignore the exception
}
}
}

public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
_ws.Dispose();
}
}
4 changes: 2 additions & 2 deletions src/Playwright.Tests/BrowserTypeConnectOverCDPTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ public async Task ShouldPrintCustomWsCloseError()
{
Server.OnceWebSocketConnection(async (ws, request) =>
{
await ws.ReceiveAsync(new byte[1024], CancellationToken.None);
await ws.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "Oh my!", CancellationToken.None);
await ws._ws.ReceiveAsync(new byte[1024], CancellationToken.None);
await ws._ws.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "Oh my!", CancellationToken.None);
});
var error = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(() => BrowserType.ConnectOverCDPAsync($"ws://localhost:{Server.Port}/ws"));
StringAssert.Contains("Browser logs:\n\nOh my!\n", error.Message);
Expand Down
4 changes: 2 additions & 2 deletions src/Playwright.Tests/BrowserTypeConnectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -608,8 +608,8 @@ public async Task ShouldPrintCustomWsCloseError()
{
Server.OnceWebSocketConnection(async (webSocket, _) =>
{
await webSocket.ReceiveAsync(new byte[1], CancellationToken.None);
await webSocket.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.PolicyViolation, "Oh my!", CancellationToken.None);
await webSocket._ws.ReceiveAsync(new byte[1], CancellationToken.None);
await webSocket._ws.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.PolicyViolation, "Oh my!", CancellationToken.None);
});
var error = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(() => BrowserType.ConnectAsync($"ws://localhost:{Server.Port}/ws"));
StringAssert.Contains("Oh my!", error.Message);
Expand Down
4 changes: 2 additions & 2 deletions src/Playwright.Tests/Locator/LocatorFrameTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ public async Task ShouldWorkForAnd()
public async Task ShouldWaitForFrame()
{
await Page.GotoAsync(Server.EmptyPage);
var error = await Page.FrameLocator("iframe").Locator("span").ClickAsync(new() { Timeout = 1000 }).ContinueWith(t => t.Exception.InnerException);
StringAssert.Contains("waiting for FrameLocator(\"iframe\")", error.Message);
var error = await Page.Locator("body").FrameLocator("iframe").Locator("span").ClickAsync(new() { Timeout = 1000 }).ContinueWith(t => t.Exception.InnerException);
StringAssert.Contains("waiting for Locator(\"body\").Locator(\"iframe\").ContentFrame.Locator(\"span\")", error.Message);
}

[PlaywrightTest("locator-frame.spec.ts", "should wait for frame 2")]
Expand Down
Loading

0 comments on commit ed71bd3

Please sign in to comment.