chore(roll): roll Playwright to v1.49.0 (#3056)
This commit is contained in:
Родитель
8f071a8815
Коммит
7797511d4d
|
@ -3,9 +3,9 @@
|
|||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Chromium <!-- GEN:chromium-version -->130.0.6723.31<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| WebKit <!-- GEN:webkit-version -->18.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->131.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Chromium <!-- GEN:chromium-version -->131.0.6778.33<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| WebKit <!-- GEN:webkit-version -->18.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->132.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**.
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<AssemblyVersion>1.48.0</AssemblyVersion>
|
||||
<PackageVersion>$(AssemblyVersion)</PackageVersion>
|
||||
<DriverVersion>1.48.1</DriverVersion>
|
||||
<DriverVersion>1.49.0</DriverVersion>
|
||||
<ReleaseVersion>$(AssemblyVersion)</ReleaseVersion>
|
||||
<FileVersion>$(AssemblyVersion)</FileVersion>
|
||||
<NoDefaultExcludes>true</NoDefaultExcludes>
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
namespace Microsoft.Playwright.Tests;
|
||||
|
||||
public class PageAriaSnapshotTests : PageTestEx
|
||||
{
|
||||
private string _unshift(string snapshot)
|
||||
{
|
||||
var lines = snapshot.Split('\n');
|
||||
var whitespacePrefixLength = 100;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
var match = System.Text.RegularExpressions.Regex.Match(line, @"^(\s*)");
|
||||
if (match.Success && match.Groups[1].Value.Length < whitespacePrefixLength)
|
||||
whitespacePrefixLength = match.Groups[1].Value.Length;
|
||||
break;
|
||||
}
|
||||
return string.Join('\n', lines.Where(t => !string.IsNullOrWhiteSpace(t)).Select(line => line.Substring(whitespacePrefixLength)));
|
||||
}
|
||||
|
||||
private async Task CheckAndMatchSnapshot(ILocator locator, string snapshot)
|
||||
{
|
||||
Assert.AreEqual(_unshift(snapshot), await locator.AriaSnapshotAsync());
|
||||
await Expect(locator).ToMatchAriaSnapshotAsync(snapshot);
|
||||
}
|
||||
|
||||
[PlaywrightTest("page-aria-snapshot.spec.ts", "should snapshot")]
|
||||
public async Task ShouldSnapshot()
|
||||
{
|
||||
await Page.SetContentAsync("<h1>title</h1>");
|
||||
await CheckAndMatchSnapshot(Page.Locator("body"), @"
|
||||
- heading ""title"" [level=1]
|
||||
");
|
||||
}
|
||||
|
||||
[PlaywrightTest("page-aria-snapshot.spec.ts", "should snapshot list")]
|
||||
public async Task ShouldSnapshotList()
|
||||
{
|
||||
await Page.SetContentAsync(@"
|
||||
<h1>title</h1>
|
||||
<h1>title 2</h1>
|
||||
");
|
||||
await CheckAndMatchSnapshot(Page.Locator("body"), @"
|
||||
- heading ""title"" [level=1]
|
||||
- heading ""title 2"" [level=1]
|
||||
");
|
||||
}
|
||||
|
||||
[PlaywrightTest("page-aria-snapshot.spec.ts", "should snapshot list with accessible name")]
|
||||
public async Task ShouldSnapshotListWithAccessibleName()
|
||||
{
|
||||
await Page.SetContentAsync(@"
|
||||
<ul aria-label=""my list"">
|
||||
<li>one</li>
|
||||
<li>two</li>
|
||||
</ul>
|
||||
");
|
||||
await CheckAndMatchSnapshot(Page.Locator("body"), @"
|
||||
- list ""my list"":
|
||||
- listitem: one
|
||||
- listitem: two
|
||||
");
|
||||
}
|
||||
|
||||
[PlaywrightTest("page-aria-snapshot.spec.ts", "should snapshot complex")]
|
||||
public async Task ShouldSnapshotComplex()
|
||||
{
|
||||
await Page.SetContentAsync(@"
|
||||
<ul>
|
||||
<li>
|
||||
<a href='about:blank'>link</a>
|
||||
</li>
|
||||
</ul>
|
||||
");
|
||||
await CheckAndMatchSnapshot(Page.Locator("body"), @"
|
||||
- list:
|
||||
- listitem:
|
||||
- link ""link""
|
||||
");
|
||||
}
|
||||
}
|
|
@ -284,4 +284,31 @@ public class PageRouteWebSocketTests : PageTestEx
|
|||
"close code=3008 reason=oops wasClean=true",
|
||||
]);
|
||||
}
|
||||
|
||||
[PlaywrightTest("page-route-web-socket.spec.ts", "should work with baseURL")]
|
||||
public async Task ShouldWorkWithBaseURL()
|
||||
{
|
||||
var context = await Browser.NewContextAsync(new() { BaseURL = $"http://localhost:{Server.Port}" });
|
||||
var page = await context.NewPageAsync();
|
||||
|
||||
await page.RouteWebSocketAsync("/ws", ws =>
|
||||
{
|
||||
ws.OnMessage(message =>
|
||||
{
|
||||
ws.Send(message.Text);
|
||||
});
|
||||
});
|
||||
|
||||
await SetupWS(page, Server.Port, "blob");
|
||||
|
||||
await page.EvaluateAsync(@"async () => {
|
||||
await window.wsOpened;
|
||||
window.ws.send('echo');
|
||||
}");
|
||||
await AssertAreEqualWithRetriesAsync(() => page.EvaluateAsync<string[]>("() => window.log"), new[]
|
||||
{
|
||||
"open",
|
||||
$"message: data=echo origin=ws://localhost:{Server.Port} lastEventId=",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -333,6 +333,40 @@ public class TracingTests : ContextTestEx
|
|||
}
|
||||
}
|
||||
|
||||
[PlaywrightTest("tracing.spec.ts", "should show tracing.group in the action list with location")]
|
||||
public async Task ShouldShowTracingGroupInActionList()
|
||||
{
|
||||
using var tracesDir = new TempDirectory();
|
||||
await Context.Tracing.StartAsync();
|
||||
var page = await Context.NewPageAsync();
|
||||
|
||||
await Context.Tracing.GroupAsync("outer group");
|
||||
await page.GotoAsync("data:text/html,<!DOCTYPE html><body><div>Hello world</div></body>");
|
||||
await Context.Tracing.GroupAsync("inner group 1");
|
||||
await page.Locator("body").ClickAsync();
|
||||
await Context.Tracing.GroupEndAsync();
|
||||
await Context.Tracing.GroupAsync("inner group 2");
|
||||
await Expect(page.GetByText("Hello")).ToBeVisibleAsync();
|
||||
await Context.Tracing.GroupEndAsync();
|
||||
await Context.Tracing.GroupEndAsync();
|
||||
|
||||
var tracePath = Path.Combine(tracesDir.Path, "trace.zip");
|
||||
await Context.Tracing.StopAsync(new() { Path = tracePath });
|
||||
|
||||
var (events, resources) = ParseTrace(tracePath);
|
||||
var actions = GetActions(events);
|
||||
|
||||
Assert.AreEqual(new[] {
|
||||
"BrowserContext.NewPageAsync",
|
||||
"outer group",
|
||||
"Page.GotoAsync",
|
||||
"inner group 1",
|
||||
"Locator.ClickAsync",
|
||||
"inner group 2",
|
||||
"LocatorAssertions.ToBeVisibleAsync"
|
||||
}, actions);
|
||||
}
|
||||
|
||||
private static (IReadOnlyList<TraceEventEntry> Events, Dictionary<string, byte[]> Resources) ParseTrace(string path)
|
||||
{
|
||||
Dictionary<string, byte[]> resources = new();
|
||||
|
|
|
@ -185,6 +185,12 @@ public partial interface IClock
|
|||
/// Makes <c>Date.now</c> and <c>new Date()</c> return fixed fake time at all times,
|
||||
/// keeps all the timers running.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Use this method for simple scenarios where you only need to test with a predefined
|
||||
/// time. For more advanced scenarios, use <see cref="IClock.InstallAsync"/> instead.
|
||||
/// Read docs on <a href="https://playwright.dev/dotnet/docs/clock">clock emulation</a>
|
||||
/// to learn more.
|
||||
/// </para>
|
||||
/// <para>**Usage**</para>
|
||||
/// <code>
|
||||
/// await page.Clock.SetFixedTimeAsync(DateTime.Now);<br/>
|
||||
|
@ -200,6 +206,12 @@ public partial interface IClock
|
|||
/// Makes <c>Date.now</c> and <c>new Date()</c> return fixed fake time at all times,
|
||||
/// keeps all the timers running.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Use this method for simple scenarios where you only need to test with a predefined
|
||||
/// time. For more advanced scenarios, use <see cref="IClock.InstallAsync"/> instead.
|
||||
/// Read docs on <a href="https://playwright.dev/dotnet/docs/clock">clock emulation</a>
|
||||
/// to learn more.
|
||||
/// </para>
|
||||
/// <para>**Usage**</para>
|
||||
/// <code>
|
||||
/// await page.Clock.SetFixedTimeAsync(DateTime.Now);<br/>
|
||||
|
@ -211,7 +223,11 @@ public partial interface IClock
|
|||
Task SetFixedTimeAsync(DateTime time);
|
||||
|
||||
/// <summary>
|
||||
/// <para>Sets current system time but does not trigger any timers.</para>
|
||||
/// <para>
|
||||
/// Sets system time, but does not trigger any timers. Use this to test how the web
|
||||
/// page reacts to a time shift, for example switching from summer to winter time, or
|
||||
/// changing time zones.
|
||||
/// </para>
|
||||
/// <para>**Usage**</para>
|
||||
/// <code>
|
||||
/// await page.Clock.SetSystemTimeAsync(DateTime.Now);<br/>
|
||||
|
@ -223,7 +239,11 @@ public partial interface IClock
|
|||
Task SetSystemTimeAsync(string time);
|
||||
|
||||
/// <summary>
|
||||
/// <para>Sets current system time but does not trigger any timers.</para>
|
||||
/// <para>
|
||||
/// Sets system time, but does not trigger any timers. Use this to test how the web
|
||||
/// page reacts to a time shift, for example switching from summer to winter time, or
|
||||
/// changing time zones.
|
||||
/// </para>
|
||||
/// <para>**Usage**</para>
|
||||
/// <code>
|
||||
/// await page.Clock.SetSystemTimeAsync(DateTime.Now);<br/>
|
||||
|
|
|
@ -60,12 +60,7 @@ namespace Microsoft.Playwright;
|
|||
/// await page.Keyboard.PressAsync("Shift+A");
|
||||
/// </code>
|
||||
/// <para>An example to trigger select-all with the keyboard</para>
|
||||
/// <code>
|
||||
/// // on Windows and Linux<br/>
|
||||
/// await page.Keyboard.PressAsync("Control+A");<br/>
|
||||
/// // on macOS<br/>
|
||||
/// await page.Keyboard.PressAsync("Meta+A");
|
||||
/// </code>
|
||||
/// <code>await page.Keyboard.PressAsync("ControlOrMeta+A");</code>
|
||||
/// </summary>
|
||||
public partial interface IKeyboard
|
||||
{
|
||||
|
|
|
@ -125,6 +125,35 @@ public partial interface ILocator
|
|||
/// <param name="locator">Additional locator to match.</param>
|
||||
ILocator And(ILocator locator);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Captures the aria snapshot of the given element. Read more about <a href="https://playwright.dev/dotnet/docs/aria-snapshots">aria
|
||||
/// snapshots</a> and <see cref="ILocatorAssertions.ToMatchAriaSnapshotAsync"/> for
|
||||
/// the corresponding assertion.
|
||||
/// </para>
|
||||
/// <para>**Usage**</para>
|
||||
/// <code>await page.GetByRole(AriaRole.Link).AriaSnapshotAsync();</code>
|
||||
/// <para>**Details**</para>
|
||||
/// <para>
|
||||
/// This method captures the aria snapshot of the given element. The snapshot is a string
|
||||
/// that represents the state of the element and its children. The snapshot can be used
|
||||
/// to assert the state of the element in the test, or to compare it to state in the
|
||||
/// future.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The ARIA snapshot is represented using <a href="https://yaml.org/spec/1.2.2/">YAML</a>
|
||||
/// markup language:
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item><description>The keys of the objects are the roles and optional accessible names of the elements.</description></item>
|
||||
/// <item><description>The values are either text content or an array of child elements.</description></item>
|
||||
/// <item><description>Generic static text can be represented with the <c>text</c> key.</description></item>
|
||||
/// </list>
|
||||
/// <para>Below is the HTML markup and the respective ARIA snapshot:</para>
|
||||
/// </summary>
|
||||
/// <param name="options">Call options</param>
|
||||
Task<string> AriaSnapshotAsync(LocatorAriaSnapshotOptions? options = default);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Calls <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur">blur</a>
|
||||
|
|
|
@ -936,6 +936,25 @@ public partial interface ILocatorAssertions
|
|||
/// <param name="values">Expected options currently selected.</param>
|
||||
/// <param name="options">Call options</param>
|
||||
Task ToHaveValuesAsync(IEnumerable<Regex> values, LocatorAssertionsToHaveValuesOptions? options = default);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Asserts that the target element matches the given <a href="https://playwright.dev/dotnet/docs/aria-snapshots">accessibility
|
||||
/// snapshot</a>.
|
||||
/// </para>
|
||||
/// <para>**Usage**</para>
|
||||
/// <code>
|
||||
/// await page.GotoAsync("https://demo.playwright.dev/todomvc/");<br/>
|
||||
/// await Expect(page.Locator("body")).ToMatchAriaSnapshotAsync(@"<br/>
|
||||
/// - heading ""todos""<br/>
|
||||
/// - textbox ""What needs to be done?""<br/>
|
||||
/// ");
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="expected">
|
||||
/// </param>
|
||||
/// <param name="options">Call options</param>
|
||||
Task ToMatchAriaSnapshotAsync(string expected, LocatorAssertionsToMatchAriaSnapshotOptions? options = default);
|
||||
}
|
||||
|
||||
#nullable disable
|
||||
|
|
|
@ -636,8 +636,6 @@ public partial interface IPage
|
|||
/// await page.EvaluateAsync("matchMedia('(prefers-color-scheme: dark)').matches");<br/>
|
||||
/// // → true<br/>
|
||||
/// await page.EvaluateAsync("matchMedia('(prefers-color-scheme: light)').matches");<br/>
|
||||
/// // → false<br/>
|
||||
/// await page.EvaluateAsync("matchMedia('(prefers-color-scheme: no-preference)').matches");<br/>
|
||||
/// // → false
|
||||
/// </code>
|
||||
/// </summary>
|
||||
|
@ -2187,8 +2185,8 @@ public partial interface IPage
|
|||
/// </para>
|
||||
/// <code>
|
||||
/// await page.RouteWebSocketAsync("/ws", ws => {<br/>
|
||||
/// ws.OnMessage(message => {<br/>
|
||||
/// if (message == "request")<br/>
|
||||
/// ws.OnMessage(frame => {<br/>
|
||||
/// if (frame.Text == "request")<br/>
|
||||
/// ws.Send("response");<br/>
|
||||
/// });<br/>
|
||||
/// });
|
||||
|
@ -2214,8 +2212,8 @@ public partial interface IPage
|
|||
/// </para>
|
||||
/// <code>
|
||||
/// await page.RouteWebSocketAsync("/ws", ws => {<br/>
|
||||
/// ws.OnMessage(message => {<br/>
|
||||
/// if (message == "request")<br/>
|
||||
/// ws.OnMessage(frame => {<br/>
|
||||
/// if (frame.Text == "request")<br/>
|
||||
/// ws.Send("response");<br/>
|
||||
/// });<br/>
|
||||
/// });
|
||||
|
@ -2241,8 +2239,8 @@ public partial interface IPage
|
|||
/// </para>
|
||||
/// <code>
|
||||
/// await page.RouteWebSocketAsync("/ws", ws => {<br/>
|
||||
/// ws.OnMessage(message => {<br/>
|
||||
/// if (message == "request")<br/>
|
||||
/// ws.OnMessage(frame => {<br/>
|
||||
/// if (frame.Text == "request")<br/>
|
||||
/// ws.Send("response");<br/>
|
||||
/// });<br/>
|
||||
/// });
|
||||
|
|
|
@ -85,11 +85,10 @@ public partial interface IRoute
|
|||
/// </code>
|
||||
/// <para>**Details**</para>
|
||||
/// <para>
|
||||
/// Note that any overrides such as <see cref="IRoute.ContinueAsync"/> or <see cref="IRoute.ContinueAsync"/>
|
||||
/// only apply to the request being routed. If this request results in a redirect, overrides
|
||||
/// will not be applied to the new redirected request. If you want to propagate a header
|
||||
/// through redirects, use the combination of <see cref="IRoute.FetchAsync"/> and <see
|
||||
/// cref="IRoute.FulfillAsync"/> instead.
|
||||
/// The <see cref="IRoute.ContinueAsync"/> option applies to both the routed request
|
||||
/// and any redirects it initiates. However, <see cref="IRoute.ContinueAsync"/>, <see
|
||||
/// cref="IRoute.ContinueAsync"/>, and <see cref="IRoute.ContinueAsync"/> only apply
|
||||
/// to the original request and are not carried over to redirected requests.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="IRoute.ContinueAsync"/> will immediately send the request to the network,
|
||||
|
|
|
@ -119,6 +119,31 @@ public partial interface ITracing
|
|||
/// <param name="options">Call options</param>
|
||||
Task StartChunkAsync(TracingStartChunkOptions? options = default);
|
||||
|
||||
/// <summary>
|
||||
/// <para>Use <c>test.step</c> instead when available.</para>
|
||||
/// <para>
|
||||
/// Creates a new group within the trace, assigning any subsequent API calls to this
|
||||
/// group, until <see cref="ITracing.GroupEndAsync"/> is called. Groups can be nested
|
||||
/// and will be visible in the trace viewer.
|
||||
/// </para>
|
||||
/// <para>**Usage**</para>
|
||||
/// <code>
|
||||
/// // All actions between GroupAsync and GroupEndAsync<br/>
|
||||
/// // will be shown in the trace viewer as a group.<br/>
|
||||
/// await Page.Context().Tracing.GroupAsync("Open Playwright.dev > API");<br/>
|
||||
/// await Page.GotoAsync("https://playwright.dev/");<br/>
|
||||
/// await Page.GetByRole(AriaRole.Link, new() { Name = "API" }).ClickAsync();<br/>
|
||||
/// await Page.Context().Tracing.GroupEndAsync();
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <remarks><para>Use <c>test.step</c> instead when available.</para></remarks>
|
||||
/// <param name="name">Group name shown in the trace viewer.</param>
|
||||
/// <param name="options">Call options</param>
|
||||
Task GroupAsync(string name, TracingGroupOptions? options = default);
|
||||
|
||||
/// <summary><para>Closes the last group created by <see cref="ITracing.GroupAsync"/>.</para></summary>
|
||||
Task GroupEndAsync();
|
||||
|
||||
/// <summary><para>Stop tracing.</para></summary>
|
||||
/// <param name="options">Call options</param>
|
||||
Task StopAsync(TracingStopOptions? options = default);
|
||||
|
|
|
@ -43,9 +43,9 @@ namespace Microsoft.Playwright;
|
|||
/// a <c>"request"</c> with a <c>"response"</c>.
|
||||
/// </para>
|
||||
/// <code>
|
||||
/// await page.RouteWebSocketAsync("/ws", ws => {<br/>
|
||||
/// ws.OnMessage(message => {<br/>
|
||||
/// if (message == "request")<br/>
|
||||
/// await page.RouteWebSocketAsync("wss://example.com/ws", ws => {<br/>
|
||||
/// ws.OnMessage(frame => {<br/>
|
||||
/// if (frame.Text == "request")<br/>
|
||||
/// ws.Send("response");<br/>
|
||||
/// });<br/>
|
||||
/// });
|
||||
|
@ -55,6 +55,21 @@ namespace Microsoft.Playwright;
|
|||
/// route handler, Playwright assumes that WebSocket will be mocked, and opens the WebSocket
|
||||
/// inside the page automatically.
|
||||
/// </para>
|
||||
/// <para>Here is another example that handles JSON messages:</para>
|
||||
/// <code>
|
||||
/// await page.RouteWebSocketAsync("wss://example.com/ws", ws => {<br/>
|
||||
/// ws.OnMessage(frame => {<br/>
|
||||
/// using var jsonDoc = JsonDocument.Parse(frame.Text);<br/>
|
||||
/// JsonElement root = jsonDoc.RootElement;<br/>
|
||||
/// if (root.TryGetProperty("request", out JsonElement requestElement) && requestElement.GetString() == "question")<br/>
|
||||
/// {<br/>
|
||||
/// var response = new Dictionary<string, string> { ["response"] = "answer" };<br/>
|
||||
/// string jsonResponse = JsonSerializer.Serialize(response);<br/>
|
||||
/// ws.Send(jsonResponse);<br/>
|
||||
/// }<br/>
|
||||
/// });<br/>
|
||||
/// });
|
||||
/// </code>
|
||||
/// <para>**Intercepting**</para>
|
||||
/// <para>
|
||||
/// Alternatively, you may want to connect to the actual server, but intercept messages
|
||||
|
@ -70,11 +85,11 @@ namespace Microsoft.Playwright;
|
|||
/// <code>
|
||||
/// await page.RouteWebSocketAsync("/ws", ws => {<br/>
|
||||
/// var server = ws.ConnectToServer();<br/>
|
||||
/// ws.OnMessage(message => {<br/>
|
||||
/// if (message == "request")<br/>
|
||||
/// ws.OnMessage(frame => {<br/>
|
||||
/// if (frame.Text == "request")<br/>
|
||||
/// server.Send("request2");<br/>
|
||||
/// else<br/>
|
||||
/// server.Send(message);<br/>
|
||||
/// server.Send(frame.Text);<br/>
|
||||
/// });<br/>
|
||||
/// });
|
||||
/// </code>
|
||||
|
@ -100,13 +115,13 @@ namespace Microsoft.Playwright;
|
|||
/// <code>
|
||||
/// await page.RouteWebSocketAsync("/ws", ws => {<br/>
|
||||
/// var server = ws.ConnectToServer();<br/>
|
||||
/// ws.OnMessage(message => {<br/>
|
||||
/// if (message != "blocked-from-the-page")<br/>
|
||||
/// server.Send(message);<br/>
|
||||
/// ws.OnMessage(frame => {<br/>
|
||||
/// if (frame.Text != "blocked-from-the-page")<br/>
|
||||
/// server.Send(frame.Text);<br/>
|
||||
/// });<br/>
|
||||
/// server.OnMessage(message => {<br/>
|
||||
/// if (message != "blocked-from-the-server")<br/>
|
||||
/// ws.Send(message);<br/>
|
||||
/// server.OnMessage(frame => {<br/>
|
||||
/// if (frame.Text != "blocked-from-the-server")<br/>
|
||||
/// ws.Send(frame.Text);<br/>
|
||||
/// });<br/>
|
||||
/// });
|
||||
/// </code>
|
||||
|
|
|
@ -148,8 +148,8 @@ public class BrowserNewContextOptions
|
|||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Emulates <c>'prefers-colors-scheme'</c> media feature, supported values are <c>'light'</c>,
|
||||
/// <c>'dark'</c>, <c>'no-preference'</c>. See <see cref="IPage.EmulateMediaAsync"/>
|
||||
/// Emulates <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a>
|
||||
/// media feature, supported values are <c>'light'</c> and <c>'dark'</c>. See <see cref="IPage.EmulateMediaAsync"/>
|
||||
/// for more details. Passing <c>'null'</c> resets emulation to system defaults. Defaults
|
||||
/// to <c>'light'</c>.
|
||||
/// </para>
|
||||
|
|
|
@ -148,8 +148,8 @@ public class BrowserNewPageOptions
|
|||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Emulates <c>'prefers-colors-scheme'</c> media feature, supported values are <c>'light'</c>,
|
||||
/// <c>'dark'</c>, <c>'no-preference'</c>. See <see cref="IPage.EmulateMediaAsync"/>
|
||||
/// Emulates <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a>
|
||||
/// media feature, supported values are <c>'light'</c> and <c>'dark'</c>. See <see cref="IPage.EmulateMediaAsync"/>
|
||||
/// for more details. Passing <c>'null'</c> resets emulation to system defaults. Defaults
|
||||
/// to <c>'light'</c>.
|
||||
/// </para>
|
||||
|
|
|
@ -194,8 +194,8 @@ public class BrowserTypeLaunchPersistentContextOptions
|
|||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Emulates <c>'prefers-colors-scheme'</c> media feature, supported values are <c>'light'</c>,
|
||||
/// <c>'dark'</c>, <c>'no-preference'</c>. See <see cref="IPage.EmulateMediaAsync"/>
|
||||
/// Emulates <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a>
|
||||
/// media feature, supported values are <c>'light'</c> and <c>'dark'</c>. See <see cref="IPage.EmulateMediaAsync"/>
|
||||
/// for more details. Passing <c>'null'</c> resets emulation to system defaults. Defaults
|
||||
/// to <c>'light'</c>.
|
||||
/// </para>
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.Text.Json.Serialization;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Playwright;
|
||||
|
||||
public class LocatorAriaSnapshotOptions
|
||||
{
|
||||
public LocatorAriaSnapshotOptions() { }
|
||||
|
||||
public LocatorAriaSnapshotOptions(LocatorAriaSnapshotOptions clone)
|
||||
{
|
||||
if (clone == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Timeout = clone.Timeout;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Maximum time in milliseconds. Defaults to <c>30000</c> (30 seconds). Pass <c>0</c>
|
||||
/// to disable timeout. The default value can be changed by using the <see cref="IBrowserContext.SetDefaultTimeout"/>
|
||||
/// or <see cref="IPage.SetDefaultTimeout"/> methods.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[JsonPropertyName("timeout")]
|
||||
public float? Timeout { get; set; }
|
||||
}
|
||||
|
||||
#nullable disable
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.Text.Json.Serialization;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Playwright;
|
||||
|
||||
public class LocatorAssertionsToMatchAriaSnapshotOptions
|
||||
{
|
||||
public LocatorAssertionsToMatchAriaSnapshotOptions() { }
|
||||
|
||||
public LocatorAssertionsToMatchAriaSnapshotOptions(LocatorAssertionsToMatchAriaSnapshotOptions clone)
|
||||
{
|
||||
if (clone == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Timeout = clone.Timeout;
|
||||
}
|
||||
|
||||
/// <summary><para>Time to retry the assertion for in milliseconds. Defaults to <c>5000</c>.</para></summary>
|
||||
[JsonPropertyName("timeout")]
|
||||
public float? Timeout { get; set; }
|
||||
}
|
||||
|
||||
#nullable disable
|
|
@ -47,9 +47,9 @@ public class PageEmulateMediaOptions
|
|||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Emulates <c>'prefers-colors-scheme'</c> media feature, supported values are <c>'light'</c>,
|
||||
/// <c>'dark'</c>, <c>'no-preference'</c>. Passing <c>'Null'</c> disables color scheme
|
||||
/// emulation.
|
||||
/// Emulates <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a>
|
||||
/// media feature, supported values are <c>'light'</c> and <c>'dark'</c>. Passing <c>'Null'</c>
|
||||
/// disables color scheme emulation. <c>'no-preference'</c> is deprecated.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[JsonPropertyName("colorScheme")]
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.Text.Json.Serialization;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Playwright;
|
||||
|
||||
public class TracingGroupOptions
|
||||
{
|
||||
public TracingGroupOptions() { }
|
||||
|
||||
public TracingGroupOptions(TracingGroupOptions clone)
|
||||
{
|
||||
if (clone == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Location = clone.Location;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Specifies a custom location for the group to be shown in the trace viewer. Defaults
|
||||
/// to the location of the <see cref="ITracing.GroupAsync"/> call.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[JsonPropertyName("location")]
|
||||
public Location? Location { get; set; }
|
||||
}
|
||||
|
||||
#nullable disable
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Microsoft.Playwright;
|
||||
|
||||
public partial class Location
|
||||
{
|
||||
/// <summary><para></para></summary>
|
||||
[Required]
|
||||
[JsonPropertyName("file")]
|
||||
public string File { get; set; } = default!;
|
||||
|
||||
/// <summary><para></para></summary>
|
||||
[JsonPropertyName("line")]
|
||||
public int? Line { get; set; }
|
||||
|
||||
/// <summary><para></para></summary>
|
||||
[JsonPropertyName("column")]
|
||||
public int? Column { get; set; }
|
||||
}
|
||||
|
||||
#nullable disable
|
|
@ -712,7 +712,7 @@ internal class BrowserContext : ChannelOwner, IBrowserContext
|
|||
|
||||
internal async Task OnWebSocketRouteAsync(WebSocketRoute webSocketRoute)
|
||||
{
|
||||
var routeHandler = _webSocketRoutes.Find(r => r.Regex?.IsMatch(webSocketRoute.Url) == true || r.Function?.Invoke(webSocketRoute.Url) == true);
|
||||
var routeHandler = _webSocketRoutes.Find(route => route.Matches(webSocketRoute.Url));
|
||||
if (routeHandler != null)
|
||||
{
|
||||
await routeHandler.HandleAsync(webSocketRoute).ConfigureAwait(false);
|
||||
|
@ -728,21 +728,7 @@ internal class BrowserContext : ChannelOwner, IBrowserContext
|
|||
|
||||
internal string CombineUrlWithBase(string url)
|
||||
{
|
||||
var baseUrl = Options?.BaseURL;
|
||||
if (string.IsNullOrEmpty(baseUrl)
|
||||
|| (url?.StartsWith("*", StringComparison.InvariantCultureIgnoreCase) ?? false)
|
||||
|| !Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute))
|
||||
{
|
||||
return url;
|
||||
}
|
||||
|
||||
var mUri = new Uri(url, UriKind.RelativeOrAbsolute);
|
||||
if (!mUri.IsAbsoluteUri)
|
||||
{
|
||||
return new Uri(new Uri(baseUrl), mUri).ToString();
|
||||
}
|
||||
|
||||
return url;
|
||||
return URLMatch.JoinWithBaseURL(Options?.BaseURL, url);
|
||||
}
|
||||
|
||||
private Task RouteAsync(Regex urlRegex, Func<string, bool> urlFunc, Delegate handler, BrowserContextRouteOptions options)
|
||||
|
@ -796,7 +782,10 @@ internal class BrowserContext : ChannelOwner, IBrowserContext
|
|||
var patterns = RouteHandler.PrepareInterceptionPatterns(_routes);
|
||||
await SendMessageToServerAsync(
|
||||
"setNetworkInterceptionPatterns",
|
||||
patterns).ConfigureAwait(false);
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["patterns"] = patterns,
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal void OnClose()
|
||||
|
@ -913,22 +902,27 @@ internal class BrowserContext : ChannelOwner, IBrowserContext
|
|||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public Task RouteWebSocketAsync(string url, Action<IWebSocketRoute> handler)
|
||||
=> RouteWebSocketAsync(new Regex(CombineUrlWithBase(url).GlobToRegex()), null, handler);
|
||||
=> RouteWebSocketAsync(url, null, null, handler);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public Task RouteWebSocketAsync(Regex url, Action<IWebSocketRoute> handler)
|
||||
=> RouteWebSocketAsync(url, null, handler);
|
||||
=> RouteWebSocketAsync(null, url, null, handler);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public Task RouteWebSocketAsync(Func<string, bool> url, Action<IWebSocketRoute> handler)
|
||||
=> RouteWebSocketAsync(null, url, handler);
|
||||
=> RouteWebSocketAsync(null, null, url, handler);
|
||||
|
||||
private Task RouteWebSocketAsync(Regex urlRegex, Func<string, bool> urlFunc, Delegate handler)
|
||||
private Task RouteWebSocketAsync(string globMatch, Regex reMatch, Func<string, bool> funcMatch, Delegate handler)
|
||||
{
|
||||
_webSocketRoutes.Insert(0, new WebSocketRouteHandler()
|
||||
{
|
||||
Regex = urlRegex,
|
||||
Function = urlFunc,
|
||||
URL = new URLMatch()
|
||||
{
|
||||
BaseURL = Options.BaseURL,
|
||||
globMatch = globMatch,
|
||||
reMatch = reMatch,
|
||||
funcMatch = funcMatch,
|
||||
},
|
||||
Handler = handler,
|
||||
});
|
||||
return UpdateWebSocketInterceptionAsync();
|
||||
|
@ -937,7 +931,10 @@ internal class BrowserContext : ChannelOwner, IBrowserContext
|
|||
private async Task UpdateWebSocketInterceptionAsync()
|
||||
{
|
||||
var patterns = WebSocketRouteHandler.PrepareInterceptionPatterns(_webSocketRoutes);
|
||||
await SendMessageToServerAsync("setWebSocketInterceptionPatterns", patterns).ConfigureAwait(false);
|
||||
await SendMessageToServerAsync("setWebSocketInterceptionPatterns", new Dictionary<string, object>
|
||||
{
|
||||
["patterns"] = patterns,
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -593,6 +593,16 @@ internal class Locator : ILocator
|
|||
|
||||
async Task<IReadOnlyList<ILocator>> ILocator.AllAsync()
|
||||
=> Enumerable.Range(0, await CountAsync().ConfigureAwait(false)).Select(Nth).ToArray();
|
||||
|
||||
public async Task<string> AriaSnapshotAsync(LocatorAriaSnapshotOptions options = null)
|
||||
{
|
||||
var result = await _frame.SendMessageToServerAsync("ariaSnapshot", new Dictionary<string, object>
|
||||
{
|
||||
["selector"] = _selector,
|
||||
["timeout"] = options?.Timeout,
|
||||
}).ConfigureAwait(false);
|
||||
return result.Value.GetProperty("snapshot").GetString();
|
||||
}
|
||||
}
|
||||
|
||||
internal class ByRoleOptions
|
||||
|
|
|
@ -91,8 +91,7 @@ internal class LocatorAssertions : AssertionsBase, ILocatorAssertions
|
|||
|
||||
private Task ExpectTrueAsync(string expression, string message, FrameExpectOptions options)
|
||||
{
|
||||
ExpectedTextValue[] expectedText = null;
|
||||
return ExpectImplAsync(expression, expectedText, null, message, options);
|
||||
return ExpectImplAsync(expression, null as ExpectedTextValue[], null, message, options);
|
||||
}
|
||||
|
||||
public Task ToContainTextAsync(string expected, LocatorAssertionsToContainTextOptions options = null) =>
|
||||
|
@ -139,10 +138,9 @@ internal class LocatorAssertions : AssertionsBase, ILocatorAssertions
|
|||
|
||||
public Task ToHaveCountAsync(int count, LocatorAssertionsToHaveCountOptions options = null)
|
||||
{
|
||||
ExpectedTextValue[] expectedText = null;
|
||||
var commonOptions = ConvertToFrameExpectOptions(options);
|
||||
commonOptions.ExpectedNumber = count;
|
||||
return ExpectImplAsync("to.have.count", expectedText, count, "Locator expected to have count", commonOptions);
|
||||
return ExpectImplAsync("to.have.count", null as ExpectedTextValue[], count, "Locator expected to have count", commonOptions);
|
||||
}
|
||||
|
||||
public Task ToHaveCSSAsync(string name, string value, LocatorAssertionsToHaveCSSOptions options = null) =>
|
||||
|
@ -174,8 +172,7 @@ internal class LocatorAssertions : AssertionsBase, ILocatorAssertions
|
|||
var commonOptions = ConvertToFrameExpectOptions(options);
|
||||
commonOptions.ExpressionArg = name;
|
||||
commonOptions.ExpectedValue = ScriptsHelper.SerializedArgument(value);
|
||||
ExpectedTextValue[] expectedText = null;
|
||||
return ExpectImplAsync("to.have.property", expectedText, value, $"Locator expected to have JavaScript property '{name}'", commonOptions);
|
||||
return ExpectImplAsync("to.have.property", null as ExpectedTextValue[], value, $"Locator expected to have JavaScript property '{name}'", commonOptions);
|
||||
}
|
||||
|
||||
public Task ToHaveTextAsync(string expected, LocatorAssertionsToHaveTextOptions options = null) =>
|
||||
|
@ -216,4 +213,11 @@ internal class LocatorAssertions : AssertionsBase, ILocatorAssertions
|
|||
|
||||
public Task ToHaveRoleAsync(AriaRole role, LocatorAssertionsToHaveRoleOptions options = null)
|
||||
=> ExpectImplAsync("to.have.role", new ExpectedTextValue() { String = role.ToString().ToLowerInvariant() }, role, "Locator expected to have role", ConvertToFrameExpectOptions(options));
|
||||
|
||||
public Task ToMatchAriaSnapshotAsync(string expected, LocatorAssertionsToMatchAriaSnapshotOptions options = null)
|
||||
{
|
||||
var commonOptions = ConvertToFrameExpectOptions(options);
|
||||
commonOptions.ExpectedValue = ScriptsHelper.SerializedArgument(expected);
|
||||
return ExpectImplAsync("to.match.aria", null as ExpectedTextValue[], expected, "Locator expected to match Aria snapshot", commonOptions);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1284,7 +1284,10 @@ internal class Page : ChannelOwner, IPage
|
|||
private async Task UpdateInterceptionAsync()
|
||||
{
|
||||
var patterns = RouteHandler.PrepareInterceptionPatterns(_routes);
|
||||
await SendMessageToServerAsync("setNetworkInterceptionPatterns", patterns).ConfigureAwait(false);
|
||||
await SendMessageToServerAsync("setNetworkInterceptionPatterns", new Dictionary<string, object>
|
||||
{
|
||||
["patterns"] = patterns,
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal void OnClose()
|
||||
|
@ -1352,7 +1355,7 @@ internal class Page : ChannelOwner, IPage
|
|||
|
||||
private async Task OnWebSocketRouteAsync(WebSocketRoute webSocketRoute)
|
||||
{
|
||||
var routeHandler = _webSocketRoutes.Find(r => r.Regex?.IsMatch(webSocketRoute.Url) == true || r.Function?.Invoke(webSocketRoute.Url) == true);
|
||||
var routeHandler = _webSocketRoutes.Find(route => route.Matches(webSocketRoute.Url));
|
||||
if (routeHandler != null)
|
||||
{
|
||||
await routeHandler.HandleAsync(webSocketRoute).ConfigureAwait(false);
|
||||
|
@ -1585,22 +1588,27 @@ internal class Page : ChannelOwner, IPage
|
|||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public Task RouteWebSocketAsync(string url, Action<IWebSocketRoute> handler)
|
||||
=> RouteWebSocketAsync(new Regex(Context.CombineUrlWithBase(url).GlobToRegex()), null, handler);
|
||||
=> RouteWebSocketAsync(url, null, null, handler);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public Task RouteWebSocketAsync(Regex url, Action<IWebSocketRoute> handler)
|
||||
=> RouteWebSocketAsync(url, null, handler);
|
||||
=> RouteWebSocketAsync(null, url, null, handler);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public Task RouteWebSocketAsync(Func<string, bool> url, Action<IWebSocketRoute> handler)
|
||||
=> RouteWebSocketAsync(null, url, handler);
|
||||
=> RouteWebSocketAsync(null, null, url, handler);
|
||||
|
||||
private Task RouteWebSocketAsync(Regex urlRegex, Func<string, bool> urlFunc, Delegate handler)
|
||||
private Task RouteWebSocketAsync(string globMatch, Regex urlRegex, Func<string, bool> urlFunc, Delegate handler)
|
||||
{
|
||||
_webSocketRoutes.Insert(0, new WebSocketRouteHandler()
|
||||
{
|
||||
Regex = urlRegex,
|
||||
Function = urlFunc,
|
||||
URL = new URLMatch()
|
||||
{
|
||||
BaseURL = Context.Options.BaseURL,
|
||||
globMatch = globMatch,
|
||||
reMatch = urlRegex,
|
||||
funcMatch = urlFunc,
|
||||
},
|
||||
Handler = handler,
|
||||
});
|
||||
return UpdateWebSocketInterceptionAsync();
|
||||
|
@ -1609,7 +1617,10 @@ internal class Page : ChannelOwner, IPage
|
|||
private async Task UpdateWebSocketInterceptionAsync()
|
||||
{
|
||||
var patterns = WebSocketRouteHandler.PrepareInterceptionPatterns(_webSocketRoutes);
|
||||
await SendMessageToServerAsync("setWebSocketInterceptionPatterns", patterns).ConfigureAwait(false);
|
||||
await SendMessageToServerAsync("setWebSocketInterceptionPatterns", new Dictionary<string, object>
|
||||
{
|
||||
["patterns"] = patterns,
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ internal class RouteHandler
|
|||
|
||||
public int HandledCount { get; set; }
|
||||
|
||||
public static Dictionary<string, object> PrepareInterceptionPatterns(List<RouteHandler> handlers)
|
||||
public static List<Dictionary<string, object>> PrepareInterceptionPatterns(List<RouteHandler> handlers)
|
||||
{
|
||||
bool all = false;
|
||||
var patterns = new List<Dictionary<string, object>>();
|
||||
|
@ -70,19 +70,15 @@ internal class RouteHandler
|
|||
|
||||
if (all)
|
||||
{
|
||||
var allPattern = new Dictionary<string, object>
|
||||
{
|
||||
["glob"] = "**/*",
|
||||
};
|
||||
|
||||
patterns.Clear();
|
||||
patterns.Add(allPattern);
|
||||
return [
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["glob"] = "**/*",
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
["patterns"] = patterns,
|
||||
};
|
||||
return patterns;
|
||||
}
|
||||
|
||||
public async Task<bool> HandleAsync(Route route)
|
||||
|
|
|
@ -168,4 +168,14 @@ internal class Tracing : ChannelOwner, ITracing
|
|||
_connection.SetIsTracing(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Task GroupAsync(string name, TracingGroupOptions options = null)
|
||||
=> SendMessageToServerAsync("tracingGroup", new Dictionary<string, object>
|
||||
{
|
||||
["name"] = name,
|
||||
["location"] = options?.Location,
|
||||
});
|
||||
|
||||
public Task GroupEndAsync()
|
||||
=> SendMessageToServerAsync("tracingGroupEnd");
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Playwright.Helpers;
|
||||
|
||||
|
@ -32,13 +31,11 @@ namespace Microsoft.Playwright.Core;
|
|||
|
||||
internal class WebSocketRouteHandler
|
||||
{
|
||||
public Regex Regex { get; set; }
|
||||
|
||||
public Func<string, bool> Function { get; set; }
|
||||
public URLMatch URL { get; set; }
|
||||
|
||||
public Delegate Handler { get; set; }
|
||||
|
||||
public static Dictionary<string, object> PrepareInterceptionPatterns(List<WebSocketRouteHandler> handlers)
|
||||
public static List<Dictionary<string, object>> PrepareInterceptionPatterns(List<WebSocketRouteHandler> handlers)
|
||||
{
|
||||
bool all = false;
|
||||
var patterns = new List<Dictionary<string, object>>();
|
||||
|
@ -47,13 +44,16 @@ internal class WebSocketRouteHandler
|
|||
var pattern = new Dictionary<string, object>();
|
||||
patterns.Add(pattern);
|
||||
|
||||
if (handler.Regex != null)
|
||||
if (!string.IsNullOrEmpty(handler.URL.globMatch))
|
||||
{
|
||||
pattern["regexSource"] = handler.Regex.ToString();
|
||||
pattern["regexFlags"] = handler.Regex.Options.GetInlineFlags();
|
||||
pattern["glob"] = handler.URL.globMatch;
|
||||
}
|
||||
|
||||
if (handler.Function != null)
|
||||
else if (handler.URL.reMatch != null)
|
||||
{
|
||||
pattern["regexSource"] = handler.URL.reMatch.ToString();
|
||||
pattern["regexFlags"] = handler.URL.reMatch.Options.GetInlineFlags();
|
||||
}
|
||||
else
|
||||
{
|
||||
all = true;
|
||||
}
|
||||
|
@ -61,19 +61,15 @@ internal class WebSocketRouteHandler
|
|||
|
||||
if (all)
|
||||
{
|
||||
var allPattern = new Dictionary<string, object>
|
||||
return [
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["glob"] = "**/*",
|
||||
};
|
||||
|
||||
patterns.Clear();
|
||||
patterns.Add(allPattern);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
["patterns"] = patterns,
|
||||
};
|
||||
return patterns;
|
||||
}
|
||||
|
||||
public async Task HandleAsync(WebSocketRoute route)
|
||||
|
@ -85,4 +81,6 @@ internal class WebSocketRouteHandler
|
|||
}
|
||||
await route.AfterHandleAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal bool Matches(string normalisedUrl) => URL.Match(normalisedUrl);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.Playwright.Helpers;
|
||||
|
||||
public class URLMatch
|
||||
{
|
||||
public Regex reMatch { get; set; }
|
||||
|
||||
public Func<string, bool> funcMatch { get; set; }
|
||||
|
||||
public string globMatch { get; set; }
|
||||
|
||||
public string BaseURL { get; set; }
|
||||
|
||||
public bool Match(string url)
|
||||
{
|
||||
if (reMatch != null)
|
||||
{
|
||||
return reMatch.IsMatch(url);
|
||||
}
|
||||
|
||||
if (funcMatch != null)
|
||||
{
|
||||
return funcMatch(url);
|
||||
}
|
||||
|
||||
if (globMatch != null)
|
||||
{
|
||||
var globWithBaseURL = JoinWithBaseURL(BaseURL, globMatch);
|
||||
// Allow http(s) baseURL to match ws(s) urls.
|
||||
if (new Regex("^https?://").IsMatch(globWithBaseURL) && new Regex("^wss?://").IsMatch(url))
|
||||
{
|
||||
globWithBaseURL = new Regex("^http").Replace(globWithBaseURL, "ws");
|
||||
}
|
||||
return new Regex(globWithBaseURL.GlobToRegex()).IsMatch(url);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static string JoinWithBaseURL(string baseUrl, string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(baseUrl)
|
||||
|| (url?.StartsWith("*", StringComparison.InvariantCultureIgnoreCase) ?? false)
|
||||
|| !Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute))
|
||||
{
|
||||
return url;
|
||||
}
|
||||
|
||||
var mUri = new Uri(url, UriKind.RelativeOrAbsolute);
|
||||
if (!mUri.IsAbsoluteUri)
|
||||
{
|
||||
return new Uri(new Uri(baseUrl), mUri).ToString();
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче