Skip to content

Commit

Permalink
feat(rolll): roll Playwright to v1.44.0 (#2935)
Browse files Browse the repository at this point in the history
  • Loading branch information
mxschmitt committed May 14, 2024
1 parent 0dc3195 commit c1bf1d0
Show file tree
Hide file tree
Showing 56 changed files with 1,084 additions and 108 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 -->124.0.6367.29<!-- GEN:stop --> ||||
| Chromium <!-- GEN:chromium-version -->125.0.6422.26<!-- GEN:stop --> ||||
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> ||||
| Firefox <!-- GEN:firefox-version -->124.0<!-- GEN:stop --> ||||
| Firefox <!-- GEN:firefox-version -->125.0.1<!-- 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.43.0</AssemblyVersion>
<PackageVersion>$(AssemblyVersion)</PackageVersion>
<DriverVersion>1.43.0-beta-1712646596000</DriverVersion>
<DriverVersion>1.44.0-beta-1715189091000</DriverVersion>
<ReleaseVersion>$(AssemblyVersion)</ReleaseVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<NoDefaultExcludes>true</NoDefaultExcludes>
Expand Down
29 changes: 16 additions & 13 deletions src/Playwright.Tests.TestServer/SimpleServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public class SimpleServer
const int MaxMessageSize = 256 * 1024;

private readonly IDictionary<string, Action<HttpContext>> _requestWaits;
private readonly IList<Action<HttpContext>> _waitForWebSocketConnectionRequestsWaits;
private readonly IList<Func<WebSocket, 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;
Expand Down Expand Up @@ -81,7 +81,7 @@ public SimpleServer(int port, string contentRoot, bool isHttps)
EmptyPage = $"{Prefix}/empty.html";

_requestWaits = new ConcurrentDictionary<string, Action<HttpContext>>();
_waitForWebSocketConnectionRequestsWaits = new List<Action<HttpContext>>();
_waitForWebSocketConnectionRequestsWaits = [];
_routes = new ConcurrentDictionary<string, Func<HttpContext, Task>>();
_auths = new ConcurrentDictionary<string, (string username, string password)>();
_csp = new ConcurrentDictionary<string, string>();
Expand All @@ -98,11 +98,12 @@ public SimpleServer(int port, string contentRoot, bool isHttps)
{
if (context.WebSockets.IsWebSocketRequest)
{
var webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
foreach (var wait in _waitForWebSocketConnectionRequestsWaits)
{
wait(context);
_waitForWebSocketConnectionRequestsWaits.Remove(wait);
await wait(webSocket, context).ConfigureAwait(false);
}
var webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
if (_onWebSocketConnectionData != null)
{
await webSocket.SendAsync(_onWebSocketConnectionData, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);
Expand Down Expand Up @@ -287,18 +288,20 @@ public async Task<T> WaitForRequest<T>(string path, Func<HttpRequest, T> selecto

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

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

var request = await taskCompletion.Task.ConfigureAwait(false);
_waitForWebSocketConnectionRequestsWaits.Remove(entryCb);
return request;
public void OnceWebSocketConnection(Func<WebSocket, HttpContext, Task> handler)
{
_waitForWebSocketConnectionRequestsWaits.Add(handler);
}

private static bool Authenticate(string username, string password, HttpContext context)
Expand Down
17 changes: 13 additions & 4 deletions src/Playwright.Tests.TestServer/assets/input/handle-locator.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,16 @@
}, false);

close.addEventListener('click', () => {
interstitial.classList.remove('visible');
target.classList.remove('hidden');
target.classList.remove('removed');
const closeInterstitial = () => {
interstitial.classList.remove('visible');
target.classList.remove('hidden');
target.classList.remove('removed');
};

if (interstitial.classList.contains('timeout'))
setTimeout(closeInterstitial, 3000);
else
closeInterstitial();
});

let timesToShow = 0;
Expand All @@ -65,9 +72,11 @@
if (!timesToShow && event !== 'none')
target.removeEventListener(event, listener, capture === 'capture');
};
if (event === 'hide') {
if (event === 'hide' || event === 'timeout') {
target.classList.add('hidden');
listener();
if (event === 'timeout')
interstitial.classList.add('timeout');
} else if (event === 'remove') {
target.classList.add('removed');
listener();
Expand Down
32 changes: 32 additions & 0 deletions src/Playwright.Tests/Assertions/LocatorAssertionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -695,4 +695,36 @@ await Page.SetContentAsync(@"
await Expect(Page.Locator("div")).Not.ToBeInViewportAsync(new() { Ratio = 0.7f });
await Expect(Page.Locator("div")).Not.ToBeInViewportAsync(new() { Ratio = 0.8f });
}

[PlaywrightTest("page/expect-misc.spec.ts", "toHaveAccessibleName")]
public async Task ToHaveAccessibleName()
{
await Page.SetContentAsync(@"<div role=""button"" aria-label=""Hello""></div>");
await Expect(Page.Locator("div")).ToHaveAccessibleNameAsync("Hello");
await Expect(Page.Locator("div")).Not.ToHaveAccessibleNameAsync("hello");
await Expect(Page.Locator("div")).ToHaveAccessibleNameAsync("hello", new() { IgnoreCase = true });
await Expect(Page.Locator("div")).ToHaveAccessibleNameAsync(new Regex(@"ell\w"));
await Expect(Page.Locator("div")).Not.ToHaveAccessibleNameAsync(new Regex("hello"));
await Expect(Page.Locator("div")).ToHaveAccessibleNameAsync(new Regex("hello"), new() { IgnoreCase = true });
}

[PlaywrightTest("page/expect-misc.spec.ts", "toHaveAccessibleDescription")]
public async Task ToHaveAccessibleDescription()
{
await Page.SetContentAsync(@"<div role=""button"" aria-description=""Hello""></div>");
await Expect(Page.Locator("div")).ToHaveAccessibleDescriptionAsync("Hello");
await Expect(Page.Locator("div")).Not.ToHaveAccessibleDescriptionAsync("hello");
await Expect(Page.Locator("div")).ToHaveAccessibleDescriptionAsync("hello", new() { IgnoreCase = true });
await Expect(Page.Locator("div")).ToHaveAccessibleDescriptionAsync(new Regex(@"ell\w"));
await Expect(Page.Locator("div")).Not.ToHaveAccessibleDescriptionAsync(new Regex("hello"));
await Expect(Page.Locator("div")).ToHaveAccessibleDescriptionAsync(new Regex("hello"), new() { IgnoreCase = true });
}

[PlaywrightTest("page/expect-misc.spec.ts", "toHaveRole")]
public async Task ToHaveRole()
{
await Page.SetContentAsync(@"<div role=""button"">Button!</div>");
await Expect(Page.Locator("div")).ToHaveRoleAsync(AriaRole.Button);
await Expect(Page.Locator("div")).Not.ToHaveRoleAsync(AriaRole.Checkbox);
}
}
10 changes: 10 additions & 0 deletions src/Playwright.Tests/Assertions/PageAssertionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,28 @@ public async Task ShouldSupportToHaveTitleAsync()
[PlaywrightTest("playwright-test/playwright.expect.misc.spec.ts", "should support toHaveURL")]
public async Task ShouldSupportToHaveURLAsync()
{
// Pass
await Page.GotoAsync("data:text/html,<div>A</div>");
await Expect(Page).ToHaveURLAsync("data:text/html,<div>A</div>");

// Fail
await Page.GotoAsync("data:text/html,<div>B</div>");
var exception = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(() => Expect(Page).ToHaveURLAsync("wrong", new() { Timeout = 1000 }));
StringAssert.Contains("Page URL expected to be 'wrong'", exception.Message);
StringAssert.Contains("But was: 'data:text/html,<div>B</div>'", exception.Message);
StringAssert.Contains("PageAssertions.ToHaveURLAsync with timeout 1000ms", exception.Message);

// Fail with Regex
await Page.GotoAsync(Server.EmptyPage);
await Expect(Page).ToHaveURLAsync(new Regex(".*empty.html"));
await PlaywrightAssert.ThrowsAsync<PlaywrightException>(() => Expect(Page).ToHaveURLAsync(new Regex("nooo"), new() { Timeout = 1000 }));

// Pass with Regex
await Page.GotoAsync(Server.EmptyPage);
await Expect(Page).ToHaveURLAsync(Server.Prefix + "/empty.html");
await Expect(Page).Not.ToHaveURLAsync(Server.Prefix + "/foobar.html");

// With BaseURL
var page = await Browser.NewPageAsync(new() { BaseURL = Server.Prefix });
try
{
Expand All @@ -79,5 +84,10 @@ public async Task ShouldSupportToHaveURLAsync()
{
await page.CloseAsync();
}

// Support IgnoreCase
await Page.GotoAsync("data:text/html,<div>A</div>");
await Expect(Page).ToHaveURLAsync("DATA:teXT/HTml,<div>a</div>", new() { IgnoreCase = true });
await Expect(Page).ToHaveURLAsync(new Regex("DATA:teXT/HTml,<div>a</div>"), new() { IgnoreCase = true });
}
}
43 changes: 43 additions & 0 deletions src/Playwright.Tests/BrowserContextFetchTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,49 @@ public async Task ShouldAcceptBoolAndNumericParams()
Assert.AreEqual("False", receivedQueryParams["bool2"].First());
}

[PlaywrightTest("browsercontext-fetch.spec.ts", "should support repeating names in multipart/form-data")]
public async Task ShouldSupportRepeatingNamesInMultipartFormData()
{
var postBodyPromise = new TaskCompletionSource<string>();
var formData = Context.APIRequest.CreateFormData();
formData.Set("name", "John");
formData.Append("name", "Doe");
formData.Append("file", new FilePayload()
{
Name = "f1.js",
MimeType = "text/javascript",
Buffer = System.Text.Encoding.UTF8.GetBytes("var x = 10;\r\n;console.log(x);")
});
formData.Append("file", new FilePayload()
{
Name = "f2.txt",
MimeType = "text/plain",
Buffer = System.Text.Encoding.UTF8.GetBytes("hello")
});
formData.Append("file", new FilePayload()
{
Name = "blob",
MimeType = "text/plain",
Buffer = System.Text.Encoding.UTF8.GetBytes("boo")
});

var (postBody, response) = await TaskUtils.WhenAll(
Server.WaitForRequest("/empty.html", request =>
{
using StreamReader reader = new(request.Body, System.Text.Encoding.UTF8);
return reader.ReadToEndAsync().GetAwaiter().GetResult();
}),
Context.APIRequest.PostAsync(Server.EmptyPage, new() { Multipart = formData })
);

StringAssert.Contains("content-disposition: form-data; name=\"name\"\r\n\r\nJohn", postBody);
StringAssert.Contains("content-disposition: form-data; name=\"name\"\r\n\r\nDoe", postBody);
StringAssert.Contains("content-disposition: form-data; name=\"file\"; filename=\"f1.js\"\r\ncontent-type: text/javascript\r\n\r\nvar x = 10;\r\n;console.log(x);", postBody);
StringAssert.Contains("content-disposition: form-data; name=\"file\"; filename=\"f2.txt\"\r\ncontent-type: text/plain\r\n\r\nhello", postBody);
StringAssert.Contains("content-disposition: form-data; name=\"file\"; filename=\"blob\"\r\ncontent-type: text/plain\r\n\r\nboo", postBody);
Assert.AreEqual(200, response.Status);
}

private async Task ForAllMethods(IAPIRequestContext request, Func<Task<IAPIResponse>, Task> callback, string url, APIRequestContextOptions options = null)
{
var methodsToTest = new[] { "fetch", "delete", "get", "head", "patch", "post", "put" };
Expand Down
2 changes: 1 addition & 1 deletion src/Playwright.Tests/BrowserContextPageEventTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public async Task ShouldWorkWithCtrlClicking()
var popupEventTask = context.WaitForPageAsync();
await TaskUtils.WhenAll(
popupEventTask,
page.ClickAsync("a", new() { Modifiers = new[] { TestConstants.IsMacOSX ? KeyboardModifier.Meta : KeyboardModifier.Control } }));
page.ClickAsync("a", new() { Modifiers = new[] { KeyboardModifier.ControlOrMeta } }));

Assert.Null(await popupEventTask.Result.OpenerAsync());
}
Expand Down
16 changes: 14 additions & 2 deletions src/Playwright.Tests/BrowserTypeConnectOverCDPTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ public async Task ShouldConnectToAnExistingCDPSession()
}
finally
{

await browserServer.CloseAsync();
}
}
Expand All @@ -63,7 +62,7 @@ public async Task ShouldSendExtraHeadersWithConnectRequest()
{ "x-foo-bar", "fookek" }
},
}).IgnoreException();
var req = await waitForRequest;
(_, var req) = await waitForRequest;
Assert.AreEqual("fookek", req.Headers["x-foo-bar"]);
StringAssert.Contains("Playwright", req.Headers["user-agent"]);
}
Expand Down Expand Up @@ -96,4 +95,17 @@ public async Task ShouldReportAllPagesInAnExistingBrowser()
await browserServer.CloseAsync();
}
}

[PlaywrightTest("chromium/chromium.spec.ts", "should report all pages in an existing browser")]
[Skip(SkipAttribute.Targets.Firefox, SkipAttribute.Targets.Webkit)]
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);
});
var error = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(() => BrowserType.ConnectOverCDPAsync($"ws://localhost:{Server.Port}/ws"));
StringAssert.Contains("Browser logs:\n\nOh my!\n", error.Message);
}
}
14 changes: 13 additions & 1 deletion src/Playwright.Tests/BrowserTypeConnectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public async Task ShouldSendDefaultUserAgentAndPlaywrightBrowserHeadersWithConne
["hello-foo"] = "i-am-bar",
}
}).IgnoreException();
var request = await connectionRequest;
(_, var request) = await connectionRequest;
StringAssert.Contains("Playwright", request.Headers["User-Agent"]);
Assert.AreEqual(request.Headers["hello-foo"], "i-am-bar");
Assert.AreEqual(request.Headers["x-playwright-browser"], BrowserType.Name);
Expand Down Expand Up @@ -511,6 +511,18 @@ public async Task SetInputFilesShouldPreserveLastModifiedTimestamp()
Assert.LessOrEqual(Math.Abs(timestamps[i] - expectedTimestamps[i]), 1000);
}

[PlaywrightTest("browsertype-connect.spec.ts", "should print custom ws close error")]
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);
});
var error = await PlaywrightAssert.ThrowsAsync<PlaywrightException>(() => BrowserType.ConnectAsync($"ws://localhost:{Server.Port}/ws"));
StringAssert.Contains("Oh my!", error.Message);
}

private class RemoteServer
{
private Process Process { get; set; }
Expand Down
Loading

0 comments on commit c1bf1d0

Please sign in to comment.