From e0e4da8eada8845d4bb491c3d0dc7a19e2a8195b Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Sun, 24 Nov 2024 14:50:12 +0100 Subject: [PATCH 01/85] docs(cli): fix docs rendering (#33751) --- docs/src/test-cli-js.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/test-cli-js.md b/docs/src/test-cli-js.md index e91c091fb4..72068d57cf 100644 --- a/docs/src/test-cli-js.md +++ b/docs/src/test-cli-js.md @@ -81,7 +81,7 @@ Complete set of Playwright Test options is available in the [configuration file] | Option | Description | | :- | :- | | Non-option arguments | Each argument is treated as a regular expression matched against the full test file path. Only tests from files matching the pattern will be executed. Special symbols like `$` or `*` should be escaped with `\`. In many shells/terminals you may need to quote the arguments. | -| `-c ` or `--config ` | Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}". Defaults to `playwright.config.ts` or `playwright.config.js` in the current directory. | +| `-c ` or `--config ` | Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}". Defaults to `playwright.config.ts` or `playwright.config.js` in the current directory. | | `--debug` | Run tests with Playwright Inspector. Shortcut for `PWDEBUG=1` environment variable and `--timeout=0 --max-failures=1 --headed --workers=1` options. | | `--fail-on-flaky-tests` | Fail if any test is flagged as flaky (default: false). | | `--forbid-only` | Fail if `test.only` is called (default: false). Useful on CI. | From 9d92b0d3ec5cb128b74099535502c90c25f44ef8 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 25 Nov 2024 10:09:35 +0100 Subject: [PATCH 02/85] docs(dotnet): add docs for xUnit (#33742) --- docs/src/intro-csharp.md | 57 +++++++- docs/src/languages.md | 2 +- docs/src/library-csharp.md | 2 +- docs/src/running-tests-csharp.md | 14 ++ .../src/test-assertions-csharp-java-python.md | 19 +++ docs/src/test-runners-csharp.md | 101 +++++++++++++- docs/src/trace-viewer-intro-csharp.md | 66 ++++++++- docs/src/trace-viewer.md | 130 ++++++++++++++++++ docs/src/writing-tests-csharp.md | 97 ++++++++++++- 9 files changed, 474 insertions(+), 14 deletions(-) diff --git a/docs/src/intro-csharp.md b/docs/src/intro-csharp.md index 3a6eb16e0b..c64bd9f53c 100644 --- a/docs/src/intro-csharp.md +++ b/docs/src/intro-csharp.md @@ -7,7 +7,7 @@ title: "Installation" Playwright was created specifically to accommodate the needs of end-to-end testing. Playwright supports all modern rendering engines including Chromium, WebKit, and Firefox. Test on Windows, Linux, and macOS, locally or on CI, headless or headed with native mobile emulation. -You can choose to use [MSTest base classes](./test-runners.md) or [NUnit base classes](./test-runners.md) that Playwright provides to write end-to-end tests. These classes support running tests on multiple browser engines, parallelizing tests, adjusting launch/context options and getting a [Page]/[BrowserContext] instance per test out of the box. Alternatively you can use the [library](./library.md) to manually write the testing infrastructure. +You can choose to use MSTest, NUnit, or xUnit [base classes](./test-runners.md) that Playwright provides to write end-to-end tests. These classes support running tests on multiple browser engines, parallelizing tests, adjusting launch/context options and getting a [Page]/[BrowserContext] instance per test out of the box. Alternatively you can use the [library](./library.md) to manually write the testing infrastructure. 1. Start by creating a new project with `dotnet new`. This will create the `PlaywrightTests` directory which includes a `UnitTest1.cs` file: @@ -17,6 +17,7 @@ You can choose to use [MSTest base classes](./test-runners.md) or [NUnit base cl values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -34,6 +35,14 @@ dotnet new mstest -n PlaywrightTests cd PlaywrightTests ``` + + + +```bash +dotnet new xunit -n PlaywrightTests +cd PlaywrightTests +``` + @@ -45,6 +54,7 @@ cd PlaywrightTests values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -60,6 +70,13 @@ dotnet add package Microsoft.Playwright.NUnit dotnet add package Microsoft.Playwright.MSTest ``` + + + +```bash +dotnet add package Microsoft.Playwright.Xunit +``` + @@ -87,6 +104,7 @@ Edit the `UnitTest1.cs` file with the code below to create an example end-to-end values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -164,6 +182,41 @@ public class ExampleTest : PageTest ``` + + +```csharp title="UnitTest1.cs" +using System.Text.RegularExpressions; +using Microsoft.Playwright; +using Microsoft.Playwright.Xunit; + +namespace PlaywrightTests; + +public class UnitTest1: PageTest +{ + [Fact] + public async Task HasTitle() + { + await Page.GotoAsync("https://playwright.dev"); + + // Expect a title "to contain" a substring. + await Expect(Page).ToHaveTitleAsync(new Regex("Playwright")); + } + + [Fact] + public async Task GetStartedLink() + { + await Page.GotoAsync("https://playwright.dev"); + + // Click the get started link. + await Page.GetByRole(AriaRole.Link, new() { Name = "Get started" }).ClickAsync(); + + // Expects page to have a heading with the name of Installation. + await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Installation" })).ToBeVisibleAsync(); + } +} +``` + + ## Running the Example Tests @@ -190,4 +243,4 @@ See our doc on [Running and Debugging Tests](./running-tests.md) to learn more a - [Generate tests with Codegen](./codegen-intro.md) - [See a trace of your tests](./trace-viewer-intro.md) - [Run tests on CI](./ci-intro.md) -- [Learn more about the MSTest and NUnit base classes](./test-runners.md) +- [Learn more about the MSTest, NUnit, and xUnit base classes](./test-runners.md) diff --git a/docs/src/languages.md b/docs/src/languages.md index 8e362def84..c8e8c8ac26 100644 --- a/docs/src/languages.md +++ b/docs/src/languages.md @@ -30,7 +30,7 @@ You can choose any testing framework such as JUnit or TestNG based on your proje ## .NET -Playwright for .NET comes with [MSTest base classes](https://playwright.dev/dotnet/docs/test-runners) and [NUnit base classes](https://playwright.dev/dotnet/docs/test-runners) for writing end-to-end tests. +Playwright for .NET comes with MSTest, NUnit, and xUnit [base classes](https://playwright.dev/dotnet/docs/test-runners) for writing end-to-end tests. * [Documentation](https://playwright.dev/dotnet/docs/intro) * [GitHub repo](https://github.com/microsoft/playwright-dotnet) diff --git a/docs/src/library-csharp.md b/docs/src/library-csharp.md index fba2d12ba2..2bba6a6683 100644 --- a/docs/src/library-csharp.md +++ b/docs/src/library-csharp.md @@ -5,7 +5,7 @@ title: "Getting started - Library" ## Introduction -Playwright can either be used with the [MSTest](./test-runners.md) or [NUnit](./test-runners.md), or as a Playwright Library (this guide). If you are working on an application that utilizes Playwright capabilities or you are using Playwright with another test runner, read on. +Playwright can either be used with the [MSTest, NUnit, or xUnit base classes](./test-runners.md) or as a Playwright Library (this guide). If you are working on an application that utilizes Playwright capabilities or you are using Playwright with another test runner, read on. ## Usage diff --git a/docs/src/running-tests-csharp.md b/docs/src/running-tests-csharp.md index 3f6a0f23e1..0b2fed27b3 100644 --- a/docs/src/running-tests-csharp.md +++ b/docs/src/running-tests-csharp.md @@ -113,6 +113,7 @@ dotnet test --filter "Name~GetStartedLink" values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -128,6 +129,19 @@ dotnet test -- NUnit.NumberOfTestWorkers=5 dotnet test -- MSTest.Parallelize.Workers=5 ``` + + + +```bash +dotnet test -- xUnit.MaxParallelThreads=5 +``` + +See [here](https://xunit.net/docs/running-tests-in-parallel.html) for more information to run tests in parallel with xUnit. + +:::note +We recommend xUnit 2.8+ which uses the [`conservative` parallelism algorithm](https://xunit.net/docs/running-tests-in-parallel.html#algorithms) by default. +::: + diff --git a/docs/src/test-assertions-csharp-java-python.md b/docs/src/test-assertions-csharp-java-python.md index 3e8e634a07..a220d9ac30 100644 --- a/docs/src/test-assertions-csharp-java-python.md +++ b/docs/src/test-assertions-csharp-java-python.md @@ -81,6 +81,7 @@ expect.set_options(timeout=10_000) values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -127,6 +128,24 @@ public class UnitTest1 : PageTest } ``` + + + +```csharp title="UnitTest1.cs" +using Microsoft.Playwright; +using Microsoft.Playwright.Xunit; + +namespace PlaywrightTests; + +public class UnitTest1: PageTest +{ + UnitTest1() + { + SetDefaultExpectTimeout(10_000); + } + // ... +} +``` diff --git a/docs/src/test-runners-csharp.md b/docs/src/test-runners-csharp.md index e1446f3f0c..baea64bbdb 100644 --- a/docs/src/test-runners-csharp.md +++ b/docs/src/test-runners-csharp.md @@ -5,7 +5,7 @@ title: "Test Runners" ## Introduction -While Playwright for .NET isn't tied to a particular test runner or testing framework, in our experience the easiest way of getting started is by using the base classes we provide for MSTest and NUnit. These classes support running tests on multiple browser engines, adjusting launch/context options and getting a [Page]/[BrowserContext] instance per test out of the box. +While Playwright for .NET isn't tied to a particular test runner or testing framework, in our experience the easiest way of getting started is by using the base classes we provide for MSTest, NUnit, or xUnit. These classes support running tests on multiple browser engines, adjusting launch/context options and getting a [Page]/[BrowserContext] instance per test out of the box. Playwright and Browser instances will be reused between tests for better performance. We recommend running each test case in a new BrowserContext, this way browser state will be @@ -17,6 +17,7 @@ isolated between the tests. values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -28,6 +29,11 @@ Playwright provides base classes to write tests with NUnit via the [`Microsoft.P Playwright provides base classes to write tests with MSTest via the [`Microsoft.Playwright.MSTest`](https://www.nuget.org/packages/Microsoft.Playwright.MSTest) package. + + + +Playwright provides base classes to write tests with xUnit via the [`Microsoft.Playwright.Xunit`](https://www.nuget.org/packages/Microsoft.Playwright.Xunit) package. + @@ -41,6 +47,7 @@ Check out the [installation guide](./intro.md) to get started. values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -64,6 +71,20 @@ Running tests in parallel at the method level (`ExecutionScope.MethodLevel`) is dotnet test --settings:.runsettings -- MSTest.Parallelize.Workers=4 ``` + + + +By default xUnit will run all classes in parallel, while running tests inside each class sequentially. +It will create by default as many processes as there are cores on the system. You can adjust this behavior by using the following CLI parameter or using a `.runsettings` file, see below. + +```bash +dotnet test -- xUnit.MaxParallelThreads=5 +``` + +:::note +We recommend xUnit 2.8+ which uses the [`conservative` parallelism algorithm](https://xunit.net/docs/running-tests-in-parallel.html#algorithms) by default. +::: + @@ -76,6 +97,7 @@ dotnet test --settings:.runsettings -- MSTest.Parallelize.Workers=4 values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -154,6 +176,41 @@ public class ExampleTest : PageTest ``` + + + +To customize context options, you can override the `ContextOptions` method of your test class derived from `Microsoft.Playwright.Xunit.PageTest` or `Microsoft.Playwright.Xunit.ContextTest`. See the following example: + +```csharp +using Microsoft.Playwright; +using Microsoft.Playwright.Xunit; + +namespace PlaywrightTests; + +public class UnitTest1 : PageTest +{ + [Fact] + public async Task TestWithCustomContextOptions() + { + // The following Page (and BrowserContext) instance has the custom colorScheme, viewport and baseURL set: + await Page.GotoAsync("/login"); + } + public override BrowserNewContextOptions ContextOptions() + { + return new BrowserNewContextOptions() + { + ColorScheme = ColorScheme.Light, + ViewportSize = new() + { + Width = 1920, + Height = 1080 + }, + BaseURL = "https://github.com", + }; + } +} +``` + @@ -194,6 +251,7 @@ When running tests from Visual Studio, you can take advantage of the `.runsettin values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -259,6 +317,36 @@ For example, to specify the number of workers, you can use `MSTest.Parallelize.W ``` + + + +For example, to specify the number of workers, you can use `xUnit.MaxParallelThreads`. You can also enable `DEBUG` logs using `RunConfiguration.EnvironmentVariables`. + +```xml + + + + + 1 + + + + + + pw:api + + + + + chromium + 5000 + + false + msedge + + + +``` @@ -270,6 +358,7 @@ For example, to specify the number of workers, you can use `MSTest.Parallelize.W values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -281,6 +370,11 @@ There are a few base classes available to you in `Microsoft.Playwright.NUnit` na There are a few base classes available to you in `Microsoft.Playwright.MSTest` namespace: + + + +There are a few base classes available to you in `Microsoft.Playwright.Xunit` namespace: + @@ -290,8 +384,3 @@ There are a few base classes available to you in `Microsoft.Playwright.MSTest` n |ContextTest |Each test will get a fresh copy of a [BrowserContext]. You can create as many pages in this context as you'd like. Using this test is the easiest way to test multi-page scenarios where you need more than one tab.



Note: You can override the `ContextOptions` method in each test file to control context options, the ones typically passed into the [`method: Browser.newContext`] method. That way you can specify all kinds of emulation options for your test file individually.| |BrowserTest |Each test will get a browser and can create as many contexts as it likes. Each test is responsible for cleaning up all the contexts it created.| |PlaywrightTest|This gives each test a Playwright object so that the test could start and stop as many browsers as it likes.| - -## xUnit support - -While using xUnit is also supported, we do not support running parallel tests. This is a well known problem/design limitation -outlined by the maintainers across [several](https://github.com/xunit/xunit/issues/2003) [issues](https://github.com/xunit/xunit/issues/2111#issuecomment-650004247). diff --git a/docs/src/trace-viewer-intro-csharp.md b/docs/src/trace-viewer-intro-csharp.md index 79e560862c..a3c463c5a5 100644 --- a/docs/src/trace-viewer-intro-csharp.md +++ b/docs/src/trace-viewer-intro-csharp.md @@ -22,6 +22,7 @@ Traces can be recorded using the [`property: BrowserContext.tracing`] API as fol values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -112,6 +113,69 @@ public class ExampleTest : PageTest } ``` + + + +```csharp +using System.Reflection; +using Microsoft.Playwright; +using Microsoft.Playwright.Xunit; +using Xunit.Sdk; + +namespace PlaywrightTests; + +[WithTestName] +public class UnitTest1 : PageTest +{ + public override async Task InitializeAsync() + { + await base.InitializeAsync().ConfigureAwait(false); + await Context.Tracing.StartAsync(new() + { + Title = $"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}", + Screenshots = true, + Snapshots = true, + Sources = true + }); + } + + public override async Task DisposeAsync() + { + await Context.Tracing.StopAsync(new() + { + Path = Path.Combine( + Environment.CurrentDirectory, + "playwright-traces", + $"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}.zip" + ) + }); + await base.DisposeAsync().ConfigureAwait(false); + } + + [Fact] + public async Task GetStartedLink() + { + // ... + await Page.GotoAsync("https://playwright.dev/dotnet/docs/intro"); + } +} + +public class WithTestNameAttribute : BeforeAfterTestAttribute +{ + public static string CurrentTestName = string.Empty; + public static string CurrentClassName = string.Empty; + + public override void Before(MethodInfo methodInfo) + { + CurrentTestName = methodInfo.Name; + CurrentClassName = methodInfo.DeclaringType!.Name; + } + + public override void After(MethodInfo methodInfo) + { + } +} +``` @@ -134,4 +198,4 @@ Check out our detailed guide on [Trace Viewer](/trace-viewer.md) to learn more a ## What's next - [Run tests on CI with GitHub Actions](/ci-intro.md) -- [Learn more about the MSTest and NUnit base classes](./test-runners.md) +- [Learn more about the MSTest, NUnit, and xUnit base classes](./test-runners.md) diff --git a/docs/src/trace-viewer.md b/docs/src/trace-viewer.md index 55338c41b9..2663595bb1 100644 --- a/docs/src/trace-viewer.md +++ b/docs/src/trace-viewer.md @@ -254,6 +254,7 @@ Traces can be recorded using the [`property: BrowserContext.tracing`] API as fol values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -348,6 +349,70 @@ public class UnitTest1 : PageTest } ``` + + + +```csharp +using System.Reflection; +using Microsoft.Playwright; +using Microsoft.Playwright.Xunit; +using Xunit.Sdk; + +namespace PlaywrightTests; + +[WithTestName] +public class UnitTest1 : PageTest +{ + public override async Task InitializeAsync() + { + await base.InitializeAsync().ConfigureAwait(false); + await Context.Tracing.StartAsync(new() + { + Title = $"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}", + Screenshots = true, + Snapshots = true, + Sources = true + }); + } + + public override async Task DisposeAsync() + { + await Context.Tracing.StopAsync(new() + { + Path = Path.Combine( + Environment.CurrentDirectory, + "playwright-traces", + $"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}.zip" + ) + }); + await base.DisposeAsync().ConfigureAwait(false); + } + + [Fact] + public async Task GetStartedLink() + { + // ... + await Page.GotoAsync("https://playwright.dev/dotnet/docs/intro"); + } +} + +public class WithTestNameAttribute : BeforeAfterTestAttribute +{ + public static string CurrentTestName = string.Empty; + public static string CurrentClassName = string.Empty; + + public override void Before(MethodInfo methodInfo) + { + CurrentTestName = methodInfo.Name; + CurrentClassName = methodInfo.DeclaringType!.Name; + } + + public override void After(MethodInfo methodInfo) + { + } +} +``` + @@ -365,6 +430,7 @@ Setup your tests to record a trace only when the test fails: values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -459,6 +525,70 @@ public class ExampleTest : PageTest } ``` + + + +```csharp +using System.Reflection; +using Microsoft.Playwright; +using Microsoft.Playwright.Xunit; +using Xunit.Sdk; + +namespace PlaywrightTests; + +[WithTestName] +public class UnitTest1 : PageTest +{ + public override async Task InitializeAsync() + { + await base.InitializeAsync().ConfigureAwait(false); + await Context.Tracing.StartAsync(new() + { + Title = $"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}", + Screenshots = true, + Snapshots = true, + Sources = true + }); + } + + public override async Task DisposeAsync() + { + await Context.Tracing.StopAsync(new() + { + Path = !TestOk ? Path.Combine( + Environment.CurrentDirectory, + "playwright-traces", + $"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}.zip" + ) : null + }); + await base.DisposeAsync().ConfigureAwait(false); + } + + [Fact] + public async Task GetStartedLink() + { + // ... + await Page.GotoAsync("https://playwright.dev/dotnet/docs/intro"); + } +} + +public class WithTestNameAttribute : BeforeAfterTestAttribute +{ + public static string CurrentTestName = string.Empty; + public static string CurrentClassName = string.Empty; + + public override void Before(MethodInfo methodInfo) + { + CurrentTestName = methodInfo.Name; + CurrentClassName = methodInfo.DeclaringType!.Name; + } + + public override void After(MethodInfo methodInfo) + { + } +} +``` + diff --git a/docs/src/writing-tests-csharp.md b/docs/src/writing-tests-csharp.md index f6324be9f1..2f3873159a 100644 --- a/docs/src/writing-tests-csharp.md +++ b/docs/src/writing-tests-csharp.md @@ -39,6 +39,7 @@ Take a look at the following example to see how to write a test. values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -117,6 +118,40 @@ public class ExampleTest : PageTest } ``` + + + +```csharp title="UnitTest1.cs" +using System.Text.RegularExpressions; +using Microsoft.Playwright; +using Microsoft.Playwright.Xunit; + +namespace PlaywrightTests; + +public class UnitTest1: PageTest +{ + [Fact] + public async Task HasTitle() + { + await Page.GotoAsync("https://playwright.dev"); + + // Expect a title "to contain" a substring. + await Expect(Page).ToHaveTitleAsync(new Regex("Playwright")); + } + + [Fact] + public async Task GetStartedLink() + { + await Page.GotoAsync("https://playwright.dev"); + + // Click the get started link. + await Page.GetByRole(AriaRole.Link, new() { Name = "Get started" }).ClickAsync(); + + // Expects page to have a heading with the name of Installation. + await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Installation" })).ToBeVisibleAsync(); + } +} +``` @@ -204,6 +239,7 @@ The Playwright NUnit and MSTest test framework base classes will isolate each te values={[ {label: 'MSTest', value: 'mstest'}, {label: 'NUnit', value: 'nunit'}, + {label: 'xUnit', value: 'xunit'}, ] }> @@ -248,23 +284,43 @@ public class ExampleTest : PageTest } ``` + + + +```csharp title="UnitTest1.cs" +using Microsoft.Playwright; +using Microsoft.Playwright.Xunit; + +namespace PlaywrightTests; + +public class UnitTest1: PageTest +{ + [Fact] + public async Task BasicTest() + { + await Page.GotoAsync("https://playwright.dev"); + } +} +``` + ## Using Test Hooks -You can use `SetUp`/`TearDown` in NUnit or `TestInitialize`/`TestCleanup` in MSTest to prepare and clean up your test environment: - +You can use `SetUp`/`TearDown` to prepare and clean up your test environment: + ```csharp title="UnitTest1.cs" using System.Threading.Tasks; using Microsoft.Playwright.NUnit; @@ -294,6 +350,8 @@ public class ExampleTest : PageTest +You can use `TestInitialize`/`TestCleanup` to prepare and clean up your test environment: + ```csharp title="UnitTest1.cs" using System.Threading.Tasks; using Microsoft.Playwright.MSTest; @@ -319,6 +377,39 @@ public class ExampleTest : PageTest } ``` + + + +You can use `InitializeAsync`/`DisposeAsync` to prepare and clean up your test environment: + +```csharp title="UnitTest1.cs" +using Microsoft.Playwright; +using Microsoft.Playwright.Xunit; + +namespace PlaywrightTests; + +public class UnitTest1: PageTest +{ + [Fact] + public async Task MainNavigation() + { + // Assertions use the expect API. + await Expect(Page).ToHaveURLAsync("https://playwright.dev/"); + } + + override public async Task InitializeAsync() + { + await base.InitializeAsync(); + await Page.GotoAsync("https://playwright.dev"); + } + + public override async Task DisposeAsync() + { + Console.WriteLine("After each test cleanup"); + await base.DisposeAsync(); + } +} +``` @@ -328,4 +419,4 @@ public class ExampleTest : PageTest - [Generate tests with Codegen](./codegen-intro.md) - [See a trace of your tests](./trace-viewer-intro.md) - [Run tests on CI](./ci-intro.md) -- [Learn more about the MSTest and NUnit base classes](./test-runners.md) +- [Learn more about the MSTest, NUnit, or xUnit base classes](./test-runners.md) From 39285c4667a735f40cf95345407fc9f4ea8622fd Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 25 Nov 2024 09:13:20 +0000 Subject: [PATCH 03/85] docs: update extensions doc for new headless (#33753) --- docs/src/chrome-extensions-js-python.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/src/chrome-extensions-js-python.md b/docs/src/chrome-extensions-js-python.md index f34ecf8e9d..1142e9b3a7 100644 --- a/docs/src/chrome-extensions-js-python.md +++ b/docs/src/chrome-extensions-js-python.md @@ -214,16 +214,15 @@ def test_popup_page(page: Page, extension_id: str) -> None: ## Headless mode -By default, Chrome's headless mode in Playwright does not support Chrome extensions. To overcome this limitation, you can run Chrome's persistent context with a new headless mode by using the following code: +By default, Chrome's headless mode in Playwright does not support Chrome extensions. To overcome this limitation, you can run Chrome's persistent context with a new headless mode by using [channel `chromium`](./browsers.md#opt-in-to-new-headless-mode): ```js title="fixtures.ts" // ... const pathToExtension = path.join(__dirname, 'my-extension'); const context = await chromium.launchPersistentContext('', { - headless: false, + channel: 'chromium', args: [ - `--headless=new`, `--disable-extensions-except=${pathToExtension}`, `--load-extension=${pathToExtension}`, ], @@ -235,9 +234,8 @@ const context = await chromium.launchPersistentContext('', { path_to_extension = Path(__file__).parent.joinpath("my-extension") context = playwright.chromium.launch_persistent_context( "", - headless=False, + channel="chromium", args=[ - "--headless=new", f"--disable-extensions-except={path_to_extension}", f"--load-extension={path_to_extension}", ], From 3fd5174b9f06fa3570761437bf60b8c9dc551377 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:35:34 -0800 Subject: [PATCH 04/85] feat(webkit): roll to r2111 (#33759) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index e4b95c9a59..b1cf873f05 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -33,7 +33,7 @@ }, { "name": "webkit", - "revision": "2110", + "revision": "2111", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", From b9c923f87c23b4c9a1671868fc87dc6c75efc7ad Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Tue, 26 Nov 2024 05:26:35 -0800 Subject: [PATCH 05/85] feat(chromium-tip-of-tree): roll to r1281 (#33769) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index b1cf873f05..123bf8c249 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1280", + "revision": "1281", "installByDefault": false, - "browserVersion": "133.0.6850.0" + "browserVersion": "133.0.6860.0" }, { "name": "firefox", From 84df6e3297f3473a2b9862f1a0328a7639f1ee63 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 26 Nov 2024 16:23:19 +0100 Subject: [PATCH 06/85] docs(python): add note about async fixtures (#33760) --- docs/src/test-runners-python.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/src/test-runners-python.md b/docs/src/test-runners-python.md index 0dd4bbeca3..3f93dd6504 100644 --- a/docs/src/test-runners-python.md +++ b/docs/src/test-runners-python.md @@ -259,3 +259,18 @@ def test_bing_is_working(page): ## Deploy to CI See the [guides for CI providers](./ci.md) to deploy your tests to CI/CD. + +## Async Fixtures + +If you want to use async fixtures, you can use the [`pytest-playwright-asyncio`](https://pypi.org/project/pytest-playwright-asyncio/) plugin. +Make sure to use `pytest-asyncio>=0.24.0` and make your tests use of [`loop_scope=sesion`](https://pytest-asyncio.readthedocs.io/en/latest/how-to-guides/run_session_tests_in_same_loop.html). + +```python +import pytest +from playwright.async_api import Page + +@pytest.mark.asyncio(loop_scope="session") +async def test_foo(page: Page): + await page.goto("https://github.com") + # ... +``` From f3ae940684dd26de1d4674d66dd98b8722b08d50 Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Tue, 26 Nov 2024 18:42:36 +0200 Subject: [PATCH 07/85] fix(ct-vue): Upgrade plugin-vue to be compatible with Vite 5 (#33758) --- package-lock.json | 13 +++++++------ packages/playwright-ct-vue/package.json | 2 +- tests/components/ct-vue-vite/package.json | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index c216b1281c..6a8526b8c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2171,14 +2171,15 @@ } }, "node_modules/@vitejs/plugin-vue": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz", - "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.0.tgz", + "integrity": "sha512-7n7KdUEtx/7Yl7I/WVAMZ1bEb0eVvXF3ummWTeLcs/9gvo9pJhuLdouSXGjdZ/MKD1acf1I272+X0RMua4/R3g==", + "license": "MIT", "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "vite": "^4.0.0 || ^5.0.0", + "vite": "^5.0.0", "vue": "^3.2.25" } }, @@ -7876,7 +7877,7 @@ "license": "Apache-2.0", "dependencies": { "@playwright/experimental-ct-core": "1.50.0-next", - "@vitejs/plugin-vue": "^4.2.1" + "@vitejs/plugin-vue": "^5.2.0" }, "bin": { "playwright": "cli.js" diff --git a/packages/playwright-ct-vue/package.json b/packages/playwright-ct-vue/package.json index 4849ca1ccb..fe48461394 100644 --- a/packages/playwright-ct-vue/package.json +++ b/packages/playwright-ct-vue/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@playwright/experimental-ct-core": "1.50.0-next", - "@vitejs/plugin-vue": "^4.2.1" + "@vitejs/plugin-vue": "^5.2.0" }, "bin": { "playwright": "cli.js" diff --git a/tests/components/ct-vue-vite/package.json b/tests/components/ct-vue-vite/package.json index 6fac022cb5..127be784a3 100644 --- a/tests/components/ct-vue-vite/package.json +++ b/tests/components/ct-vue-vite/package.json @@ -12,7 +12,7 @@ "vue-router": "^4.1.5" }, "devDependencies": { - "@vitejs/plugin-vue": "^4.1.0", + "@vitejs/plugin-vue": "^5.2.0", "@vue/tsconfig": "^0.5.1", "typescript": "5.6.2", "vite": "^5.2.8", From ff6c283af5d96079011acf98c8263820e6f27d74 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Wed, 27 Nov 2024 07:53:22 -0800 Subject: [PATCH 08/85] feat(webkit): roll to r2112 (#33778) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 123bf8c249..f37e718223 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -33,7 +33,7 @@ }, { "name": "webkit", - "revision": "2111", + "revision": "2112", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", From a84488edaaa32702f23eb6d5374598e6d2ed7712 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 28 Nov 2024 11:21:52 +0000 Subject: [PATCH 09/85] fix(aria): escape even more yaml (#33793) --- .../src/server/injected/yaml.ts | 24 +++------ tests/page/page-aria-snapshot.spec.ts | 54 +++++++++++++++++++ 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/packages/playwright-core/src/server/injected/yaml.ts b/packages/playwright-core/src/server/injected/yaml.ts index e87c6706de..7daebc574a 100644 --- a/packages/playwright-core/src/server/injected/yaml.ts +++ b/packages/playwright-core/src/server/injected/yaml.ts @@ -62,12 +62,8 @@ function yamlStringNeedsQuotes(str: string): boolean { if (/^-\s/.test(str)) return true; - // Strings that start with a special indicator character need quotes - if (/^[&*\],].*/.test(str)) - return true; - - // Strings containing ':' followed by a space or at the end need quotes - if (/:(\s|$)/.test(str)) + // Strings containing ':' or '\n' followed by a space or at the end need quotes + if (/[\n:](\s|$)/.test(str)) return true; // Strings containing '#' preceded by a space need quotes (comment indicator) @@ -78,21 +74,17 @@ function yamlStringNeedsQuotes(str: string): boolean { if (/[\n\r]/.test(str)) return true; - // Strings starting with '?' or '!' (directives) need quotes - if (/^[?!]/.test(str)) - return true; - - // Strings starting with '>' or '|' (block scalar indicators) need quotes - if (/^[>|]/.test(str)) - return true; - - // Strings starting with quotes need quotes - if (/^["']/.test(str)) + // Strings starting with indicator characters or quotes need quotes + if (/^[&*\],?!>|@"'#%]/.test(str)) return true; // Strings containing special characters that could cause ambiguity if (/[{}`]/.test(str)) return true; + // Non-string types recognized by YAML + if (!isNaN(Number(str)) || ['y', 'n', 'yes', 'no', 'true', 'false', 'on', 'off', 'null'].includes(str.toLowerCase())) + return true; + return false; } diff --git a/tests/page/page-aria-snapshot.spec.ts b/tests/page/page-aria-snapshot.spec.ts index 3acf1dbb1a..81e1beadfa 100644 --- a/tests/page/page-aria-snapshot.spec.ts +++ b/tests/page/page-aria-snapshot.spec.ts @@ -508,3 +508,57 @@ it('should handle long strings', async ({ page }) => { - region: ${s} `); }); + +it('should escape special yaml characters', async ({ page }) => { + await page.setContent(` + @hello@hello + ]hello]hello + hello\n + hello\n\n hello\n hello + #hello#hello + `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - link "@hello" + - text: "@hello" + - link "]hello" + - text: "]hello" + - link "hello" + - text: hello + - link "hello" + - text: hello + - link "#hello" + - text: "#hello" + `); +}); + +it('should escape special yaml values', async ({ page }) => { + await page.setContent(` + trueFalse + NOyes + yN + onOff + nullNULL + 123123 + -1.2-1.2 + + `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - link "true" + - text: "False" + - link "NO" + - text: "yes" + - link "y" + - text: "N" + - link "on" + - text: "Off" + - link "null" + - text: "NULL" + - link "123" + - text: "123" + - link "-1.2" + - text: "-1.2" + - textbox: "555" + `); +}); From 4fb6c4ed4ccd30cb211d76981e582de9ebfa11bf Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 28 Nov 2024 14:04:34 +0100 Subject: [PATCH 10/85] fix(trace): in `indexTree` check `isVisible` before adding to result (#33797) --- packages/web/src/components/treeView.tsx | 8 ++++--- tests/playwright-test/ui-mode-trace.spec.ts | 24 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/web/src/components/treeView.tsx b/packages/web/src/components/treeView.tsx index e6cf557e99..242e834ede 100644 --- a/packages/web/src/components/treeView.tsx +++ b/packages/web/src/components/treeView.tsx @@ -319,7 +319,9 @@ function indexTree( selectedItem: T | undefined, expandedItems: Map, autoExpandDepth: number, - isVisible?: (item: T) => boolean): Map { + isVisible: (item: T) => boolean = () => true): Map { + if (!isVisible(rootItem)) + return new Map(); const result = new Map(); const temporaryExpanded = new Set(); @@ -328,9 +330,9 @@ function indexTree( let lastItem: T | null = null; const appendChildren = (parent: T, depth: number) => { - if (isVisible && !isVisible(parent)) - return; for (const item of parent.children as T[]) { + if (!isVisible(item)) + continue; const expandState = temporaryExpanded.has(item.id) || expandedItems.get(item.id); const autoExpandMatches = autoExpandDepth > depth && result.size < 25 && expandState !== false; const expanded = item.children.length ? expandState ?? autoExpandMatches : undefined; diff --git a/tests/playwright-test/ui-mode-trace.spec.ts b/tests/playwright-test/ui-mode-trace.spec.ts index eb56204813..16ebeaf499 100644 --- a/tests/playwright-test/ui-mode-trace.spec.ts +++ b/tests/playwright-test/ui-mode-trace.spec.ts @@ -339,3 +339,27 @@ test('should show request source context id', async ({ runUITest, server }) => { await expect(page.getByText('page#2')).toBeVisible(); await expect(page.getByText('api#1')).toBeVisible(); }); + +test('should filter actions tab on double-click', async ({ runUITest, server }) => { + const { page } = await runUITest({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('pass', async ({ page }) => { + await page.goto('${server.EMPTY_PAGE}'); + }); + `, + }); + + await page.getByText('pass').dblclick(); + + const actionsTree = page.getByTestId('actions-tree'); + await expect(actionsTree.getByRole('treeitem')).toHaveText([ + /Before Hooks/, + /page.goto/, + /After Hooks/, + ]); + await actionsTree.getByRole('treeitem', { name: 'page.goto' }).dblclick(); + await expect(actionsTree.getByRole('treeitem')).toHaveText([ + /page.goto/, + ]); +}); From fd25f3ab8540a644da56cc011c7e7202e21fd045 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 28 Nov 2024 05:43:45 -0800 Subject: [PATCH 11/85] feat(chromium-tip-of-tree): roll to r1282 (#33798) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index f37e718223..3dba3152a7 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1281", + "revision": "1282", "installByDefault": false, - "browserVersion": "133.0.6860.0" + "browserVersion": "133.0.6864.0" }, { "name": "firefox", From b5bd543cc6935f62732da3b6183bd319eb2bdc2a Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 28 Nov 2024 14:36:49 +0000 Subject: [PATCH 12/85] test: skip 'should not auto play audio' with frozen time (#33799) --- tests/library/capabilities.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/library/capabilities.spec.ts b/tests/library/capabilities.spec.ts index 7526359c20..03ff1d7feb 100644 --- a/tests/library/capabilities.spec.ts +++ b/tests/library/capabilities.spec.ts @@ -436,6 +436,7 @@ it('should not auto play audio', { } }, async ({ page, browserName, isWindows }) => { it.fixme(browserName === 'webkit' && isWindows); + it.skip(process.env.PW_CLOCK === 'frozen', 'no way to inject real setTimeout'); await page.route('**/*', async route => { await route.fulfill({ status: 200, From b456ac5f8cf88abab51197272629923d4488a70b Mon Sep 17 00:00:00 2001 From: Tasawar Hussain <31658686+tasawar-hussain@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:36:50 +0500 Subject: [PATCH 13/85] docs: update ci.md (#33815) Signed-off-by: Tasawar Hussain <31658686+tasawar-hussain@users.noreply.github.com> --- docs/src/ci.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/ci.md b/docs/src/ci.md index 99033a3e2e..54e0a7a749 100644 --- a/docs/src/ci.md +++ b/docs/src/ci.md @@ -454,11 +454,11 @@ jobs: ### Docker -We have a [pre-built Docker image](./docker.md) which can either be used directly, or as a reference to update your existing Docker definitions. +We have a [pre-built Docker image](./docker.md) which can either be used directly or as a reference to update your existing Docker definitions. Suggested configuration 1. Using `--ipc=host` is also recommended when using Chromium. Without it Chromium can run out of memory - and crash. Learn more about this option in [Docker docs](https://docs.docker.com/engine/reference/run/#ipc-settings---ipc). + and crash. Learn more about this option in [Docker docs](https://docs.docker.com/reference/cli/docker/container/run/#ipc). 1. Seeing other weird errors when launching Chromium? Try running your container with `docker run --cap-add=SYS_ADMIN` when developing locally. 1. Using `--init` Docker flag or [dumb-init](https://github.com/Yelp/dumb-init) is recommended to avoid special @@ -466,7 +466,7 @@ Suggested configuration ### Azure Pipelines -For Windows or macOS agents, no additional configuration required, just install Playwright and run your tests. +For Windows or macOS agents, no additional configuration is required, just install Playwright and run your tests. For Linux agents, you can use [our Docker container](./docker.md) with Azure Pipelines support [running containerized From 4e33ade28754a8cdc9fbc0302d3dc3a0f3eb05c0 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 29 Nov 2024 14:13:53 +0000 Subject: [PATCH 14/85] docs: fix codegen --viewport option examples (#33816) --- docs/src/codegen.md | 26 ++++++++++----------- packages/playwright-core/src/cli/program.ts | 6 +++-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/src/codegen.md b/docs/src/codegen.md index 4b45f12d01..f0d9a605f5 100644 --- a/docs/src/codegen.md +++ b/docs/src/codegen.md @@ -21,7 +21,7 @@ Install the VS Code extension and generate tests directly from VS Code. The exte ### Record a New Test -To record a test click on the **Record new** button from the Testing sidebar. This will create a `test-1.spec.ts` file as well as open up a browser window. +To record a test click on the **Record new** button from the Testing sidebar. This will create a `test-1.spec.ts` file as well as open up a browser window. record new in vs code @@ -58,16 +58,16 @@ In the test file in VS Code you will see your new generated actions added to you ### Generating locators -You can generate locators with the test generator. -- Click on the **Pick locator** button form the testing sidebar and then hover over elements in the browser window to see the [locator](./locators.md) highlighted underneath each element. -- Click the element you require and it will now show up in the **Pick locator** box in VS Code. +You can generate locators with the test generator. +- Click on the **Pick locator** button form the testing sidebar and then hover over elements in the browser window to see the [locator](./locators.md) highlighted underneath each element. +- Click the element you require and it will now show up in the **Pick locator** box in VS Code. - Press Enter on your keyboard to copy the locator into the clipboard and then paste anywhere in your code. Or press 'escape' if you want to cancel. Pick locators in VS code ## Generate tests with the Playwright Inspector -When running the `codegen` command two windows will be opened, a browser window where you interact with the website you wish to test and the Playwright Inspector window where you can record your tests and then copy them into your editor. +When running the `codegen` command two windows will be opened, a browser window where you interact with the website you wish to test and the Playwright Inspector window where you can record your tests and then copy them into your editor. ### Running Codegen @@ -128,10 +128,10 @@ When you have finished interacting with the page, press the **record** button to Use the **clear** button to clear the code to start recording again. Once finished, close the Playwright inspector window or stop the terminal command. ### Generating locators -You can generate [locators](/locators.md) with the test generator. +You can generate [locators](/locators.md) with the test generator. * Press the `'Record'` button to stop the recording and the `'Pick Locator'` button will appear. -* Click on the `'Pick Locator'` button and then hover over elements in the browser window to see the locator highlighted underneath each element. +* Click on the `'Pick Locator'` button and then hover over elements in the browser window to see the locator highlighted underneath each element. * To choose a locator, click on the element you would like to locate and the code for that locator will appear in the field next to the Pick Locator button. * You can then edit the locator in this field to fine tune it or use the copy button to copy it and paste it into your code. @@ -164,19 +164,19 @@ You can use the test generator to generate tests using emulation so as to genera Playwright opens a browser window with its viewport set to a specific width and height and is not responsive as tests need to be run under the same conditions. Use the `--viewport` option to generate tests with a different viewport size. ```bash js -npx playwright codegen --viewport-size=800,600 playwright.dev +npx playwright codegen --viewport-size="800,600" playwright.dev ``` ```bash java -mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="codegen --viewport-size=800,600 playwright.dev" +mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="codegen --viewport-size='800,600' playwright.dev" ``` ```bash python -playwright codegen --viewport-size=800,600 playwright.dev +playwright codegen --viewport-size="800,600" playwright.dev ``` ```bash csharp -pwsh bin/Debug/netX/playwright.ps1 codegen --viewport-size=800,600 playwright.dev +pwsh bin/Debug/netX/playwright.ps1 codegen --viewport-size="800,600" playwright.dev ``` ###### * langs: js @@ -325,7 +325,7 @@ pwsh bin/Debug/netX/playwright.ps1 codegen --timezone="Europe/Rome" --geolocatio ### Preserve authenticated state -Run `codegen` with `--save-storage` to save [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) and [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) at the end of the session. This is useful to separately record an authentication step and reuse it later when recording more tests. +Run `codegen` with `--save-storage` to save [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) and [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) at the end of the session. This is useful to separately record an authentication step and reuse it later when recording more tests. ```bash js npx playwright codegen github.com/microsoft/playwright --save-storage=auth.json @@ -367,7 +367,7 @@ pwsh bin/Debug/netX/playwright.ps1 codegen github.com/microsoft/playwright --sav #### Login -After performing authentication and closing the browser, `auth.json` will contain the storage state which you can then reuse in your tests. +After performing authentication and closing the browser, `auth.json` will contain the storage state which you can then reuse in your tests. login to GitHub screen diff --git a/packages/playwright-core/src/cli/program.ts b/packages/playwright-core/src/cli/program.ts index 6cf0403446..7d0659d26f 100644 --- a/packages/playwright-core/src/cli/program.ts +++ b/packages/playwright-core/src/cli/program.ts @@ -449,10 +449,12 @@ async function launchContext(options: Options, extraOptions: LaunchOptions): Pro // Viewport size if (options.viewportSize) { try { - const [width, height] = options.viewportSize.split(',').map(n => parseInt(n, 10)); + const [width, height] = options.viewportSize.split(',').map(n => +n); + if (isNaN(width) || isNaN(height)) + throw new Error('bad values'); contextOptions.viewport = { width, height }; } catch (e) { - throw new Error('Invalid viewport size format: use "width, height", for example --viewport-size=800,600'); + throw new Error('Invalid viewport size format: use "width,height", for example --viewport-size="800,600"'); } } From 9b5f7f77b22858a1db5cbc0efe5c9dc55e7aeb75 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 2 Dec 2024 16:20:44 -0800 Subject: [PATCH 15/85] docs(network): add proxy back-ref to api docs (#33839) --- docs/src/network.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/src/network.md b/docs/src/network.md index 97c637a806..fc2471a321 100644 --- a/docs/src/network.md +++ b/docs/src/network.md @@ -115,8 +115,7 @@ await page.GotoAsync("https://example.com"); You can configure pages to load over the HTTP(S) proxy or SOCKSv5. Proxy can be either set globally for the entire browser, or for each browser context individually. -You can optionally specify username and password for HTTP(S) proxy, you can also specify hosts to -bypass proxy for. +You can optionally specify username and password for HTTP(S) proxy, you can also specify hosts to bypass the [`option: Browser.newContext.proxy`] for. Here is an example of a global proxy: From e4211ee3acd4c68779601e999ab03356ece63334 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Tue, 3 Dec 2024 07:14:05 -0800 Subject: [PATCH 16/85] feat(chromium-tip-of-tree): roll to r1283 (#33845) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 3dba3152a7..8388e1e248 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1282", + "revision": "1283", "installByDefault": false, - "browserVersion": "133.0.6864.0" + "browserVersion": "133.0.6874.0" }, { "name": "firefox", From a7f2868594e5d60233d342322284642f26de6b78 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 3 Dec 2024 17:25:14 +0000 Subject: [PATCH 17/85] fix(codegen): do not reset current tool upon clearing highlight (#33822) --- .../playwright-core/src/server/injected/recorder/recorder.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/playwright-core/src/server/injected/recorder/recorder.ts b/packages/playwright-core/src/server/injected/recorder/recorder.ts index ddfb0386d9..7374035706 100644 --- a/packages/playwright-core/src/server/injected/recorder/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder/recorder.ts @@ -1157,7 +1157,6 @@ export class Recorder { } clearHighlight() { - this._currentTool.cleanup?.(); this.updateHighlight(null, false); } From abf6916909ba8fb576668bbcd5876887cd5d12d9 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:05:51 -0800 Subject: [PATCH 18/85] feat(webkit): roll to r2113 (#33807) --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 8388e1e248..4071882647 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -33,7 +33,7 @@ }, { "name": "webkit", - "revision": "2112", + "revision": "2113", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", From be78e9e11f3de5c75b7c52f27a4a95c5c733afdb Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 3 Dec 2024 23:55:45 +0000 Subject: [PATCH 19/85] fix: do not stall waiting for pending navigations after beforeunload dismiss (#33834) --- .../src/server/chromium/crPage.ts | 3 +++ .../src/server/webkit/wkPage.ts | 3 +++ tests/library/beforeunload.spec.ts | 22 +++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index f3f21121e5..bfad678f1c 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -846,6 +846,9 @@ class FrameSession { event.type, event.message, async (accept: boolean, promptText?: string) => { + // TODO: this should actually be a CDP event that notifies about a cancelled navigation attempt. + if (this._isMainFrame() && event.type === 'beforeunload' && !accept) + this._page._frameManager.frameAbortedNavigation(this._page.mainFrame()._id, 'navigation cancelled by beforeunload dialog'); await this._client.send('Page.handleJavaScriptDialog', { accept, promptText }); }, event.defaultPrompt)); diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index 13096075f7..a6e2145ce4 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -611,6 +611,9 @@ export class WKPage implements PageDelegate { event.type as dialog.DialogType, event.message, async (accept: boolean, promptText?: string) => { + // TODO: this should actually be a RDP event that notifies about a cancelled navigation attempt. + if (event.type === 'beforeunload' && !accept) + this._page._frameManager.frameAbortedNavigation(this._page.mainFrame()._id, 'navigation cancelled by beforeunload dialog'); await this._pageProxySession.send('Dialog.handleJavaScriptDialog', { accept, promptText }); }, event.defaultPrompt)); diff --git a/tests/library/beforeunload.spec.ts b/tests/library/beforeunload.spec.ts index 9f0982eea7..05134bd429 100644 --- a/tests/library/beforeunload.spec.ts +++ b/tests/library/beforeunload.spec.ts @@ -104,3 +104,25 @@ it('should not stall on evaluate when dismissing beforeunload', async ({ page, s ]); }); +it('should not stall on click when dismissing beforeunload', async ({ page, server }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/33806' }); + + await page.goto(server.EMPTY_PAGE); + await page.setContent(`click me`); + + await page.evaluate(() => { + window.onbeforeunload = () => false; + }); + page.on('dialog', async dialog => { + await dialog.dismiss(); + }); + + await page.getByRole('link').click({ noWaitAfter: true }); + await page.evaluate(() => { + window.onbeforeunload = null; + }); + + // This line should not timeout. + await page.getByRole('link').click({ timeout: 5000 }); + await expect(page).toHaveURL(server.PREFIX + '/frames/one-frame.html'); +}); From b941359fce94f3dd1c0ec17ebd22860cc3fcb647 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 5 Dec 2024 06:08:44 -0800 Subject: [PATCH 20/85] chore(driver): roll driver to recent Node.js LTS version (#33870) --- utils/build/build-playwright-driver.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/build/build-playwright-driver.sh b/utils/build/build-playwright-driver.sh index f89e770c77..9c87e01cb5 100755 --- a/utils/build/build-playwright-driver.sh +++ b/utils/build/build-playwright-driver.sh @@ -4,7 +4,7 @@ set -x trap "cd $(pwd -P)" EXIT SCRIPT_PATH="$(cd "$(dirname "$0")" ; pwd -P)" -NODE_VERSION="22.11.0" # autogenerated via ./update-playwright-driver-version.mjs +NODE_VERSION="22.12.0" # autogenerated via ./update-playwright-driver-version.mjs cd "$(dirname "$0")" PACKAGE_VERSION=$(node -p "require('../../package.json').version") From b86725bb98a92cc0ba77f33e65937d7c3b464b59 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 5 Dec 2024 09:34:16 -0800 Subject: [PATCH 21/85] feat(chromium): roll to r1151 (#33873) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 4071882647..1fa33ba167 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,15 +3,15 @@ "browsers": [ { "name": "chromium", - "revision": "1150", + "revision": "1151", "installByDefault": true, - "browserVersion": "132.0.6834.15" + "browserVersion": "132.0.6834.32" }, { "name": "chromium-headless-shell", - "revision": "1150", + "revision": "1151", "installByDefault": true, - "browserVersion": "132.0.6834.15" + "browserVersion": "132.0.6834.32" }, { "name": "chromium-tip-of-tree", From ee8208beda1156f29aac8869570b273f2c135c0e Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 5 Dec 2024 09:34:30 -0800 Subject: [PATCH 22/85] feat(chromium-tip-of-tree): roll to r1284 (#33876) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 1fa33ba167..6f5d925d12 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1283", + "revision": "1284", "installByDefault": false, - "browserVersion": "133.0.6874.0" + "browserVersion": "133.0.6878.0" }, { "name": "firefox", From f6f6a6225c5c8b5a6dc6208c8e0f8bcc2952d844 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 5 Dec 2024 15:44:41 -0800 Subject: [PATCH 23/85] docs: locale defaults to en-us (#33840) --- docs/src/test-api/class-testoptions.md | 5 ++++- packages/playwright/types/test.d.ts | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/src/test-api/class-testoptions.md b/docs/src/test-api/class-testoptions.md index 20dad210e7..c285430616 100644 --- a/docs/src/test-api/class-testoptions.md +++ b/docs/src/test-api/class-testoptions.md @@ -392,8 +392,11 @@ export default defineConfig({ }); ``` -## property: TestOptions.locale = %%-context-option-locale-%% +## property: TestOptions.locale * since: v1.10 +- type: <[string]> + +Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` request header value as well as number and date formatting rules. Defaults to `en-US`. Learn more about emulation in our [emulation guide](../emulation.md#locale--timezone). **Usage** diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 8d05bdafef..fed6a1bdeb 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -6247,8 +6247,8 @@ export interface PlaywrightTestOptions { javaScriptEnabled: boolean; /** * Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, - * `Accept-Language` request header value as well as number and date formatting rules. Defaults to the system default - * locale. Learn more about emulation in our [emulation guide](https://playwright.dev/docs/emulation#locale--timezone). + * `Accept-Language` request header value as well as number and date formatting rules. Defaults to `en-US`. Learn more + * about emulation in our [emulation guide](https://playwright.dev/docs/emulation#locale--timezone). * * **Usage** * From 993546c1bc3267fb72eddaf8cf003cb2e1519598 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 5 Dec 2024 16:22:52 -0800 Subject: [PATCH 24/85] chore: r1284 fixup (#33883) --- README.md | 4 +- .../src/server/deviceDescriptorsSource.json | 96 +++++++++---------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 625e13fb11..6203130f2e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🎭 Playwright -[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-132.0.6834.15-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-132.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-132.0.6834.32-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-132.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) @@ -8,7 +8,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 132.0.6834.15 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 132.0.6834.32 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 18.2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 132.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index ea07b3140a..179eb52f7b 100644 --- a/packages/playwright-core/src/server/deviceDescriptorsSource.json +++ b/packages/playwright-core/src/server/deviceDescriptorsSource.json @@ -110,7 +110,7 @@ "defaultBrowserType": "webkit" }, "Galaxy S5": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -121,7 +121,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -132,7 +132,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 360, "height": 740 @@ -143,7 +143,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 740, "height": 360 @@ -154,7 +154,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 320, "height": 658 @@ -165,7 +165,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+ landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 658, "height": 320 @@ -176,7 +176,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", "viewport": { "width": 712, "height": 1138 @@ -187,7 +187,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -1098,7 +1098,7 @@ "defaultBrowserType": "webkit" }, "LG Optimus L70": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1109,7 +1109,7 @@ "defaultBrowserType": "chromium" }, "LG Optimus L70 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1120,7 +1120,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1131,7 +1131,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1142,7 +1142,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1153,7 +1153,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1164,7 +1164,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1175,7 +1175,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1186,7 +1186,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1197,7 +1197,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1208,7 +1208,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1219,7 +1219,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1230,7 +1230,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1241,7 +1241,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1252,7 +1252,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1263,7 +1263,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1274,7 +1274,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1285,7 +1285,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1296,7 +1296,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1307,7 +1307,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -1362,7 +1362,7 @@ "defaultBrowserType": "webkit" }, "Pixel 2": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -1373,7 +1373,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -1384,7 +1384,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -1395,7 +1395,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -1406,7 +1406,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -1417,7 +1417,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -1428,7 +1428,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -1439,7 +1439,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -1450,7 +1450,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -1465,7 +1465,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -1480,7 +1480,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -1495,7 +1495,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -1510,7 +1510,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -1525,7 +1525,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -1540,7 +1540,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1551,7 +1551,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1562,7 +1562,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -1577,7 +1577,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Safari/537.36 Edg/132.0.6834.15", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36 Edg/132.0.6834.32", "screen": { "width": 1792, "height": 1120 @@ -1622,7 +1622,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -1637,7 +1637,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 Safari/537.36 Edg/132.0.6834.15", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36 Edg/132.0.6834.32", "screen": { "width": 1920, "height": 1080 From 733f9a2926a86374e71823150484c7c2c34d162f Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 5 Dec 2024 17:53:31 -0800 Subject: [PATCH 25/85] chore: pdf generation now works in headed mode too (#33879) --- docs/src/api/class-page.md | 4 ---- packages/playwright-core/types/types.d.ts | 2 -- tests/library/pdf.spec.ts | 10 ++++------ 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index f65a904932..f44bd18696 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -2762,10 +2762,6 @@ This method requires Playwright to be started in a headed mode, with a falsy [`o Returns the PDF buffer. -:::note -Generating a pdf is currently only supported in Chromium headless. -::: - `page.pdf()` generates a pdf of the page with `print` css media. To generate a pdf with `screen` media, call [`method: Page.emulateMedia`] before calling `page.pdf()`: diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 7086d87f2e..34b83cfc52 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -3609,8 +3609,6 @@ export interface Page { /** * Returns the PDF buffer. * - * **NOTE** Generating a pdf is currently only supported in Chromium headless. - * * `page.pdf()` generates a pdf of the page with `print` css media. To generate a pdf with `screen` media, call * [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#page-emulate-media) before calling * `page.pdf()`: diff --git a/tests/library/pdf.spec.ts b/tests/library/pdf.spec.ts index 3ef002e25c..38e8c7b5be 100644 --- a/tests/library/pdf.spec.ts +++ b/tests/library/pdf.spec.ts @@ -17,9 +17,8 @@ import { browserTest as it, expect } from '../config/browserTest'; import fs from 'fs'; -it('should be able to save file', async ({ contextFactory, headless, browserName }, testInfo) => { - it.skip(!headless || browserName !== 'chromium', 'Printing to pdf is currently only supported in headless chromium.'); - +it('should be able to save file', async ({ contextFactory, browserName }, testInfo) => { + it.skip(browserName !== 'chromium', 'Printing to pdf is currently only supported in chromium.'); const context = await contextFactory(); const page = await context.newPage(); const outputFile = testInfo.outputPath('output.pdf'); @@ -27,9 +26,8 @@ it('should be able to save file', async ({ contextFactory, headless, browserName expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0); }); -it('should be able to generate outline', async ({ contextFactory, server, headless, browserName }, testInfo) => { - it.skip(!headless || browserName !== 'chromium', 'Printing to pdf is currently only supported in headless chromium.'); - // const context = await contextFactory(); +it('should be able to generate outline', async ({ contextFactory, server, browserName }, testInfo) => { + it.skip(browserName !== 'chromium', 'Printing to pdf is currently only supported in chromium.'); const context = await contextFactory({ baseURL: server.PREFIX, }); From a56a5cabbd1c19acdff2a096fd0ee406ee56d3e9 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Fri, 6 Dec 2024 10:09:35 -0800 Subject: [PATCH 26/85] docs: clarify use of comments in contributions (#33896) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cb06a17e77..ebcabc8a27 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,7 @@ Coding style is fully defined in [.eslintrc](https://github.com/microsoft/playwr npm run lint ``` -Comments should be generally avoided. If the code would not be understood without comments, consider re-writing the code to make it self-explanatory. +Comments should have an explicit purpose and should improve readability rather than hinder it. If the code would not be understood without comments, consider re-writing the code to make it self-explanatory. ### Write documentation From 4914f34a837bacfc7395748dc5aa0a956611c568 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 6 Dec 2024 10:17:06 -0800 Subject: [PATCH 27/85] fix(recorder): allow clearing when recording is disabled (#33821) --- .../src/server/recorder/recorderCollection.ts | 3 +- tests/library/inspector/cli-codegen-1.spec.ts | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/playwright-core/src/server/recorder/recorderCollection.ts b/packages/playwright-core/src/server/recorder/recorderCollection.ts index b5216d7382..2643dece91 100644 --- a/packages/playwright-core/src/server/recorder/recorderCollection.ts +++ b/packages/playwright-core/src/server/recorder/recorderCollection.ts @@ -38,7 +38,7 @@ export class RecorderCollection extends EventEmitter { restart() { this._actions = []; - this._fireChange(); + this.emit('change', []); } setEnabled(enabled: boolean) { @@ -128,6 +128,7 @@ export class RecorderCollection extends EventEmitter { private _fireChange() { if (!this._enabled) return; + this.emit('change', collapseActions(this._actions)); } } diff --git a/tests/library/inspector/cli-codegen-1.spec.ts b/tests/library/inspector/cli-codegen-1.spec.ts index a876a25e47..0fd5f69d0b 100644 --- a/tests/library/inspector/cli-codegen-1.spec.ts +++ b/tests/library/inspector/cli-codegen-1.spec.ts @@ -926,4 +926,34 @@ await page.GetByRole(AriaRole.Button, new() { Name = "Submit" }).ClickAsync();`) const predicate = (msg: ConsoleMessage) => msg.type() === 'error' && /Content[\- ]Security[\- ]Policy/i.test(msg.text()); await expect(page.waitForEvent('console', { predicate, timeout: 1000 })).rejects.toThrow(); }); + + test('should clear when recording is disabled', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/33802' } }, async ({ openRecorder }) => { + const { recorder } = await openRecorder(); + + await recorder.setContentAndWait(` + + + `); + + await recorder.hoverOverElement('#foo'); + let [sources] = await Promise.all([ + recorder.waitForOutput('JavaScript', 'click'), + recorder.trustedClick(), + ]); + + expect(sources.get('JavaScript').text).toContain(`getByRole('button', { name: 'Foo' }).click()`); + + await recorder.recorderPage.getByRole('button', { name: 'Record' }).click(); + await recorder.recorderPage.getByRole('button', { name: 'Clear' }).click(); + await recorder.recorderPage.getByRole('button', { name: 'Record' }).click(); + + await recorder.hoverOverElement('#bar'); + [sources] = await Promise.all([ + recorder.waitForOutput('JavaScript', 'click'), + recorder.trustedClick(), + ]); + + expect(sources.get('JavaScript').text).toContain(`getByRole('button', { name: 'Bar' }).click()`); + expect(sources.get('JavaScript').text).not.toContain(`getByRole('button', { name: 'Foo' })`); + }); }); From 2a1a9c94521a417943f676017e378a069c62a88d Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:38:51 -0800 Subject: [PATCH 28/85] feat(webkit): roll to r2116 (#33897) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 6f5d925d12..53e8334b99 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -33,7 +33,7 @@ }, { "name": "webkit", - "revision": "2113", + "revision": "2116", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", From dfa24462dbad83c404dca9443e551b8083a4d8b1 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 6 Dec 2024 16:43:25 -0800 Subject: [PATCH 29/85] feat(webkit): roll to r2117 (#33902) --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 53e8334b99..da7775185d 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -33,7 +33,7 @@ }, { "name": "webkit", - "revision": "2116", + "revision": "2117", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", From 0937d2f7b9cb3f386965e8787da4c18b2c2f651c Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 9 Dec 2024 08:59:01 -0800 Subject: [PATCH 30/85] fix(types): update types for test.extend (#33784) --- packages/playwright/types/test.d.ts | 19 +++--- .../ct-react17/tests/render.spec.tsx | 1 + tests/playwright-test/types.spec.ts | 65 +++++++++++++++++++ utils/generate_types/overrides-test.d.ts | 19 +++--- 4 files changed, 84 insertions(+), 20 deletions(-) diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index fed6a1bdeb..e9764b6ead 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -1891,7 +1891,7 @@ type ConditionBody = (args: TestArgs) => boolean; * ``` * */ -export interface TestType { +export interface TestType { /** * Declares a test. * - `test(title, body)` @@ -5632,7 +5632,7 @@ export interface TestType(fixtures: Fixtures): TestType; + extend(fixtures: Fixtures): TestType; /** * Returns information about the currently running test. This method can only be called during the test execution, * otherwise it throws. @@ -5653,19 +5653,18 @@ export interface TestType = (args: Args, use: (r: R) => Promise, testInfo: TestInfo) => any; -export type WorkerFixture = (args: Args, use: (r: R) => Promise, workerInfo: WorkerInfo) => any; -type TestFixtureValue = Exclude | TestFixture; -type WorkerFixtureValue = Exclude | WorkerFixture; -export type Fixtures = { +export type TestFixture = (args: Args, use: (r: R) => Promise, testInfo: TestInfo) => any; +export type WorkerFixture = (args: Args, use: (r: R) => Promise, workerInfo: WorkerInfo) => any; +type TestFixtureValue = Exclude | TestFixture; +type WorkerFixtureValue = Exclude | WorkerFixture; +export type Fixtures = { [K in keyof PW]?: WorkerFixtureValue | [WorkerFixtureValue, { scope: 'worker', timeout?: number | undefined, title?: string, box?: boolean }]; } & { [K in keyof PT]?: TestFixtureValue | [TestFixtureValue, { scope: 'test', timeout?: number | undefined, title?: string, box?: boolean }]; } & { - [K in keyof W]?: [WorkerFixtureValue, { scope: 'worker', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; + [K in Exclude]?: [WorkerFixtureValue, { scope: 'worker', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; } & { - [K in keyof T]?: TestFixtureValue | [TestFixtureValue, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; + [K in Exclude]?: TestFixtureValue | [TestFixtureValue, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; }; type BrowserName = 'chromium' | 'firefox' | 'webkit'; diff --git a/tests/components/ct-react17/tests/render.spec.tsx b/tests/components/ct-react17/tests/render.spec.tsx index ec9405c82f..46e3ea9abf 100644 --- a/tests/components/ct-react17/tests/render.spec.tsx +++ b/tests/components/ct-react17/tests/render.spec.tsx @@ -31,6 +31,7 @@ test('render an empty component', async ({ mount, page }) => { const testWithServer = test.extend(serverFixtures); testWithServer( 'components routing should go through context', + // @ts-ignore "serverFixtures" are imported from the impl without any types async ({ mount, context, server }) => { server.setRoute('/hello', (req: any, res: any) => { res.write('served via server'); diff --git a/tests/playwright-test/types.spec.ts b/tests/playwright-test/types.spec.ts index c34c586f7a..e9fea813f0 100644 --- a/tests/playwright-test/types.spec.ts +++ b/tests/playwright-test/types.spec.ts @@ -115,6 +115,71 @@ test('should check types of fixtures', async ({ runTSC }) => { await use(x); }, }); + + base.extend({ + page: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + }, + }); + + base.extend<{ myFixture: (arg: number) => void }>({ + page: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + }, + }); + + base.extend({ + // @ts-expect-error + myFixture: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + } + }); + + base.extend<{ myFixture: (arg: number) => void }>({ + myFixture: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + } + }); + + base.extend({ + page: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + }, + // @ts-expect-error + myFixture: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + } + }); + + base.extend<{ myFixture: (arg: number) => void }>({ + page: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + }, + myFixture: async ({ page }) => { + type IsPage = (typeof page) extends Page ? true : never; + const isPage: IsPage = true; + } + }); + + base.extend<{ myFixture: (arg: number) => void }>({ + // @ts-expect-error + myFixture: (arg: number) => {}, + }); + + base.extend<{ myFixture: (arg: number) => void }>({ + myFixture: async (_, use) => { + use((arg: number) => {}); + // @ts-expect-error + use((arg: string) => {}); + } + }); `, 'playwright.config.ts': ` import { MyOptions } from './helper'; diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 5775917382..78df18f014 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -78,7 +78,7 @@ export type TestDetails = { type TestBody = (args: TestArgs, testInfo: TestInfo) => Promise | void; type ConditionBody = (args: TestArgs) => boolean; -export interface TestType { +export interface TestType { (title: string, body: TestBody): void; (title: string, details: TestDetails, body: TestBody): void; @@ -164,23 +164,22 @@ export interface TestType): void; step(title: string, body: () => T | Promise, options?: { box?: boolean, location?: Location, timeout?: number }): Promise; expect: Expect<{}>; - extend(fixtures: Fixtures): TestType; + extend(fixtures: Fixtures): TestType; info(): TestInfo; } -type KeyValue = { [key: string]: any }; -export type TestFixture = (args: Args, use: (r: R) => Promise, testInfo: TestInfo) => any; -export type WorkerFixture = (args: Args, use: (r: R) => Promise, workerInfo: WorkerInfo) => any; -type TestFixtureValue = Exclude | TestFixture; -type WorkerFixtureValue = Exclude | WorkerFixture; -export type Fixtures = { +export type TestFixture = (args: Args, use: (r: R) => Promise, testInfo: TestInfo) => any; +export type WorkerFixture = (args: Args, use: (r: R) => Promise, workerInfo: WorkerInfo) => any; +type TestFixtureValue = Exclude | TestFixture; +type WorkerFixtureValue = Exclude | WorkerFixture; +export type Fixtures = { [K in keyof PW]?: WorkerFixtureValue | [WorkerFixtureValue, { scope: 'worker', timeout?: number | undefined, title?: string, box?: boolean }]; } & { [K in keyof PT]?: TestFixtureValue | [TestFixtureValue, { scope: 'test', timeout?: number | undefined, title?: string, box?: boolean }]; } & { - [K in keyof W]?: [WorkerFixtureValue, { scope: 'worker', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; + [K in Exclude]?: [WorkerFixtureValue, { scope: 'worker', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; } & { - [K in keyof T]?: TestFixtureValue | [TestFixtureValue, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; + [K in Exclude]?: TestFixtureValue | [TestFixtureValue, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean }]; }; type BrowserName = 'chromium' | 'firefox' | 'webkit'; From d029b03d9fc7d65329b47e08b9690b237bee30b7 Mon Sep 17 00:00:00 2001 From: yangsisi <13655750+yangsisi0422@users.noreply.github.com> Date: Tue, 10 Dec 2024 01:20:47 +0800 Subject: [PATCH 31/85] =?UTF-8?q?fix(defineConfig):=20fix=20type=20issue?= =?UTF-8?q?=20passing=20custom=20property=20in=20the=20seco=E2=80=A6=20(#3?= =?UTF-8?q?3774)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yangsisi <13655750+yangsisi0422@users.noreply.github.com> --- packages/playwright-ct-core/index.d.ts | 4 ++-- packages/playwright/types/test.d.ts | 4 ++-- utils/generate_types/overrides-test.d.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/playwright-ct-core/index.d.ts b/packages/playwright-ct-core/index.d.ts index 9c323ef6df..f6710ee1f6 100644 --- a/packages/playwright-ct-core/index.d.ts +++ b/packages/playwright-ct-core/index.d.ts @@ -52,7 +52,7 @@ export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; -export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; -export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; export { expect, devices, Locator } from 'playwright/test'; diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index e9764b6ead..5a22800191 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -7486,8 +7486,8 @@ export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; -export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; -export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; type MergedT = List extends [TestType, ...(infer Rest)] ? T & MergedT : {}; type MergedW = List extends [TestType, ...(infer Rest)] ? W & MergedW : {}; diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 78df18f014..49c6988ea7 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -484,8 +484,8 @@ export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; -export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; -export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; type MergedT = List extends [TestType, ...(infer Rest)] ? T & MergedT : {}; type MergedW = List extends [TestType, ...(infer Rest)] ? W & MergedW : {}; From 27060a0f652455b9fc60b2c28af45f4583bc1fcb Mon Sep 17 00:00:00 2001 From: jinohkang-theori Date: Tue, 10 Dec 2024 11:07:00 +0900 Subject: [PATCH 32/85] fix(connect): disable context takeover in websocket transport (#33811) --- packages/playwright-core/src/server/transport.ts | 1 + packages/playwright-core/src/utils/wsServer.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/playwright-core/src/server/transport.ts b/packages/playwright-core/src/server/transport.ts index e213e030e7..507225b04c 100644 --- a/packages/playwright-core/src/server/transport.ts +++ b/packages/playwright-core/src/server/transport.ts @@ -24,6 +24,7 @@ import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from '../utils/happy- import type { HeadersArray } from './types'; export const perMessageDeflate = { + clientNoContextTakeover: true, zlibDeflateOptions: { level: 3, }, diff --git a/packages/playwright-core/src/utils/wsServer.ts b/packages/playwright-core/src/utils/wsServer.ts index 63c0ed1fed..636a57c3cc 100644 --- a/packages/playwright-core/src/utils/wsServer.ts +++ b/packages/playwright-core/src/utils/wsServer.ts @@ -25,6 +25,7 @@ let lastConnectionId = 0; const kConnectionSymbol = Symbol('kConnection'); export const perMessageDeflate = { + serverNoContextTakeover: true, zlibDeflateOptions: { level: 3, }, From 200e868b639e672181e2da88301269fc3bb4cac6 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 10 Dec 2024 11:19:36 -0800 Subject: [PATCH 33/85] chore: bump nanoid to 3.3.8 (#33936) --- package-lock.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a8526b8c2..9341f8d676 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5396,15 +5396,16 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, From a25bda6950e25929956009766c7c93bdd48c11c1 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 10 Dec 2024 11:45:16 -0800 Subject: [PATCH 34/85] chore: allow storing aria snapshots in files (#33919) --- docs/src/api/class-locator.md | 2 +- docs/src/api/class-locatorassertions.md | 61 ++++++- docs/src/aria-snapshots.md | 2 +- docs/src/release-notes-csharp.md | 2 +- docs/src/release-notes-java.md | 2 +- docs/src/release-notes-js.md | 2 +- docs/src/release-notes-python.md | 2 +- packages/playwright-core/types/types.d.ts | 2 +- .../src/matchers/toMatchAriaSnapshot.ts | 69 +++++-- packages/playwright/types/test.d.ts | 31 ++++ .../aria-snapshot-file.spec.ts | 172 ++++++++++++++++++ .../update-aria-snapshot.spec.ts | 49 +++++ 12 files changed, 370 insertions(+), 26 deletions(-) create mode 100644 tests/playwright-test/aria-snapshot-file.spec.ts diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index e61b119c69..e93d02d9d8 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -155,7 +155,7 @@ Additional locator to match. - returns: <[string]> Captures the aria snapshot of the given element. -Read more about [aria snapshots](../aria-snapshots.md) and [`method: LocatorAssertions.toMatchAriaSnapshot`] for the corresponding assertion. +Read more about [aria snapshots](../aria-snapshots.md) and [`method: LocatorAssertions.toMatchAriaSnapshot#2`] for the corresponding assertion. **Usage** diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 7a640c4ef6..7e61e1b3a5 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -446,7 +446,7 @@ Expected options currently selected. * since: v1.49 * langs: python -The opposite of [`method: LocatorAssertions.toMatchAriaSnapshot`]. +The opposite of [`method: LocatorAssertions.toMatchAriaSnapshot#2`]. ### param: LocatorAssertions.NotToMatchAriaSnapshot.expected * since: v1.49 @@ -2121,7 +2121,58 @@ Expected options currently selected. * since: v1.23 -## async method: LocatorAssertions.toMatchAriaSnapshot +## async method: LocatorAssertions.toMatchAriaSnapshot#1 +* since: v1.50 +* langs: + - alias-java: matchesAriaSnapshot + +Asserts that the target element matches the given [accessibility snapshot](../aria-snapshots.md). + +**Usage** + +```js +await expect(page.locator('body')).toMatchAriaSnapshot(); +await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'snapshot' }); +await expect(page.locator('body')).toMatchAriaSnapshot({ path: '/path/to/snapshot.yml' }); +``` + +```python async +await expect(page.locator('body')).to_match_aria_snapshot(path='/path/to/snapshot.yml') +``` + +```python sync +expect(page.locator('body')).to_match_aria_snapshot(path='/path/to/snapshot.yml') +``` + +```csharp +await Expect(page.Locator("body")).ToMatchAriaSnapshotAsync(new { Path = "/path/to/snapshot.yml" }); +``` + +```java +assertThat(page.locator("body")).matchesAriaSnapshot(new LocatorAssertions.MatchesAriaSnapshotOptions().setPath("/path/to/snapshot.yml")); +``` + +### option: LocatorAssertions.toMatchAriaSnapshot#1.name +* since: v1.50 +* langs: js +- `name` <[string]> + +Name of the snapshot to store in the snapshot folder corresponding to this test. Generates ordinal name if not specified. + +### option: LocatorAssertions.toMatchAriaSnapshot#1.path +* since: v1.50 +- `path` <[string]> + +Path to the YAML snapshot file. + +### option: LocatorAssertions.toMatchAriaSnapshot#1.timeout = %%-js-assertions-timeout-%% +* since: v1.50 + +### option: LocatorAssertions.toMatchAriaSnapshot#1.timeout = %%-csharp-java-python-assertions-timeout-%% +* since: v1.50 + + +## async method: LocatorAssertions.toMatchAriaSnapshot#2 * since: v1.49 * langs: - alias-java: matchesAriaSnapshot @@ -2170,12 +2221,12 @@ assertThat(page.locator("body")).matchesAriaSnapshot(""" """); ``` -### param: LocatorAssertions.toMatchAriaSnapshot.expected +### param: LocatorAssertions.toMatchAriaSnapshot#2.expected * since: v1.49 - `expected` -### option: LocatorAssertions.toMatchAriaSnapshot.timeout = %%-js-assertions-timeout-%% +### option: LocatorAssertions.toMatchAriaSnapshot#2.timeout = %%-js-assertions-timeout-%% * since: v1.49 -### option: LocatorAssertions.toMatchAriaSnapshot.timeout = %%-csharp-java-python-assertions-timeout-%% +### option: LocatorAssertions.toMatchAriaSnapshot#2.timeout = %%-csharp-java-python-assertions-timeout-%% * since: v1.49 diff --git a/docs/src/aria-snapshots.md b/docs/src/aria-snapshots.md index e190fd3022..27d1c0ebce 100644 --- a/docs/src/aria-snapshots.md +++ b/docs/src/aria-snapshots.md @@ -154,7 +154,7 @@ structure of a page, use the [Chrome DevTools Accessibility Pane](https://develo ## Snapshot matching -The [`method: LocatorAssertions.toMatchAriaSnapshot`] assertion method in Playwright compares the accessible +The [`method: LocatorAssertions.toMatchAriaSnapshot#2`] assertion method in Playwright compares the accessible structure of the locator scope with a predefined aria snapshot template, helping validate the page's state against testing requirements. diff --git a/docs/src/release-notes-csharp.md b/docs/src/release-notes-csharp.md index 7c1ffc87ab..0d2681c874 100644 --- a/docs/src/release-notes-csharp.md +++ b/docs/src/release-notes-csharp.md @@ -9,7 +9,7 @@ toc_max_heading_level: 2 ### Aria snapshots -New assertion [`method: LocatorAssertions.toMatchAriaSnapshot`] verifies page structure by comparing to an expected accessibility tree, represented as YAML. +New assertion [`method: LocatorAssertions.toMatchAriaSnapshot#2`] verifies page structure by comparing to an expected accessibility tree, represented as YAML. ```csharp await page.GotoAsync("https://playwright.dev"); diff --git a/docs/src/release-notes-java.md b/docs/src/release-notes-java.md index 8130c77f07..317c1c5398 100644 --- a/docs/src/release-notes-java.md +++ b/docs/src/release-notes-java.md @@ -8,7 +8,7 @@ toc_max_heading_level: 2 ### Aria snapshots -New assertion [`method: LocatorAssertions.toMatchAriaSnapshot`] verifies page structure by comparing to an expected accessibility tree, represented as YAML. +New assertion [`method: LocatorAssertions.toMatchAriaSnapshot#2`] verifies page structure by comparing to an expected accessibility tree, represented as YAML. ```java page.navigate("https://playwright.dev"); diff --git a/docs/src/release-notes-js.md b/docs/src/release-notes-js.md index 06a0fe3b19..0595933b80 100644 --- a/docs/src/release-notes-js.md +++ b/docs/src/release-notes-js.md @@ -15,7 +15,7 @@ import LiteYouTube from '@site/src/components/LiteYouTube'; ### Aria snapshots -New assertion [`method: LocatorAssertions.toMatchAriaSnapshot`] verifies page structure by comparing to an expected accessibility tree, represented as YAML. +New assertion [`method: LocatorAssertions.toMatchAriaSnapshot#2`] verifies page structure by comparing to an expected accessibility tree, represented as YAML. ```js await page.goto('https://playwright.dev'); diff --git a/docs/src/release-notes-python.md b/docs/src/release-notes-python.md index fc7dd1ccd1..f2002f15fd 100644 --- a/docs/src/release-notes-python.md +++ b/docs/src/release-notes-python.md @@ -8,7 +8,7 @@ toc_max_heading_level: 2 ### Aria snapshots -New assertion [`method: LocatorAssertions.toMatchAriaSnapshot`] verifies page structure by comparing to an expected accessibility tree, represented as YAML. +New assertion [`method: LocatorAssertions.toMatchAriaSnapshot#2`] verifies page structure by comparing to an expected accessibility tree, represented as YAML. ```python page.goto("https://playwright.dev") diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 34b83cfc52..9d29183992 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -12428,7 +12428,7 @@ export interface Locator { /** * Captures the aria snapshot of the given element. Read more about [aria snapshots](https://playwright.dev/docs/aria-snapshots) and - * [expect(locator).toMatchAriaSnapshot(expected[, options])](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-match-aria-snapshot) + * [expect(locator).toMatchAriaSnapshot(expected[, options])](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-match-aria-snapshot-2) * for the corresponding assertion. * * **Usage** diff --git a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts index 552ad2e36d..152ceb6ba9 100644 --- a/packages/playwright/src/matchers/toMatchAriaSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchAriaSnapshot.ts @@ -18,19 +18,25 @@ import type { LocatorEx } from './matchers'; import type { ExpectMatcherState } from '../../types/test'; import { kNoElementsFoundError, matcherHint, type MatcherResult } from './matcherHint'; -import { colors } from 'playwright-core/lib/utilsBundle'; import { EXPECTED_COLOR } from '../common/expectBundle'; -import { callLogText } from '../util'; +import { callLogText, sanitizeFilePathBeforeExtension, trimLongString } from '../util'; import { printReceivedStringContainExpectedSubstring } from './expect'; import { currentTestInfo } from '../common/globals'; import type { MatcherReceived } from '@injected/ariaSnapshot'; -import { escapeTemplateString } from 'playwright-core/lib/utils'; +import { escapeTemplateString, isString, sanitizeForFilePath } from 'playwright-core/lib/utils'; +import fs from 'fs'; +import path from 'path'; + +type ToMatchAriaSnapshotExpected = { + name?: string; + path?: string; +} | string; export async function toMatchAriaSnapshot( this: ExpectMatcherState, receiver: LocatorEx, - expected: string, - options: { timeout?: number, matchSubstring?: boolean } = {}, + expectedParam: ToMatchAriaSnapshotExpected, + options: { timeout?: number } = {}, ): Promise> { const matcherName = 'toMatchAriaSnapshot'; @@ -39,7 +45,7 @@ export async function toMatchAriaSnapshot( throw new Error(`toMatchAriaSnapshot() must be called during the test`); if (testInfo._projectInternal.ignoreSnapshots) - return { pass: !this.isNot, message: () => '', name: 'toMatchAriaSnapshot', expected }; + return { pass: !this.isNot, message: () => '', name: 'toMatchAriaSnapshot', expected: '' }; const updateSnapshots = testInfo.config.updateSnapshots; @@ -48,12 +54,25 @@ export async function toMatchAriaSnapshot( promise: this.promise, }; - if (typeof expected !== 'string') { - throw new Error([ - matcherHint(this, receiver, matcherName, receiver, expected, matcherOptions), - `${colors.bold('Matcher error')}: ${EXPECTED_COLOR('expected',)} value must be a string`, - this.utils.printWithType('Expected', expected, this.utils.printExpected) - ].join('\n\n')); + let expected: string; + let expectedPath: string | undefined; + if (isString(expectedParam)) { + expected = expectedParam; + } else { + if (expectedParam?.path) { + expectedPath = expectedParam.path; + } else if (expectedParam?.name) { + expectedPath = testInfo.snapshotPath(sanitizeFilePathBeforeExtension(expectedParam.name)); + } else { + let snapshotNames = (testInfo as any)[snapshotNamesSymbol] as SnapshotNames; + if (!snapshotNames) { + snapshotNames = { anonymousSnapshotIndex: 0 }; + (testInfo as any)[snapshotNamesSymbol] = snapshotNames; + } + const fullTitleWithoutSpec = [...testInfo.titlePath.slice(1), ++snapshotNames.anonymousSnapshotIndex].join(' '); + expectedPath = testInfo.snapshotPath(sanitizeForFilePath(trimLongString(fullTitleWithoutSpec)) + '.yml'); + } + expected = await fs.promises.readFile(expectedPath, 'utf8').catch(() => ''); } const generateMissingBaseline = updateSnapshots === 'missing' && !expected; @@ -102,8 +121,24 @@ export async function toMatchAriaSnapshot( if ((updateSnapshots === 'all') || (updateSnapshots === 'changed' && pass === this.isNot) || generateMissingBaseline) { - const suggestedRebaseline = `toMatchAriaSnapshot(\`\n${escapeTemplateString(indent(typedReceived.regex, '{indent} '))}\n{indent}\`)`; - return { pass: this.isNot, message: () => '', name: 'toMatchAriaSnapshot', suggestedRebaseline }; + if (expectedPath) { + await fs.promises.mkdir(path.dirname(expectedPath), { recursive: true }); + await fs.promises.writeFile(expectedPath, typedReceived.regex, 'utf8'); + const relativePath = path.relative(process.cwd(), expectedPath); + if (updateSnapshots === 'missing') { + const message = `A snapshot doesn't exist at ${relativePath}, writing actual.`; + testInfo._hasNonRetriableError = true; + testInfo._failWithError(new Error(message)); + } else { + const message = `A snapshot is generated at ${relativePath}.`; + /* eslint-disable no-console */ + console.log(message); + } + return { pass: true, message: () => '', name: 'toMatchAriaSnapshot' }; + } else { + const suggestedRebaseline = `toMatchAriaSnapshot(\`\n${escapeTemplateString(indent(typedReceived.regex, '{indent} '))}\n{indent}\`)`; + return { pass: false, message: () => '', name: 'toMatchAriaSnapshot', suggestedRebaseline }; + } } } @@ -134,3 +169,9 @@ function unshift(snapshot: string): string { function indent(snapshot: string, indent: string): string { return snapshot.split('\n').map(line => indent + line).join('\n'); } + +const snapshotNamesSymbol = Symbol('snapshotNames'); + +type SnapshotNames = { + anonymousSnapshotIndex: number; +}; diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 5a22800191..520bcb30d3 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -8430,6 +8430,37 @@ interface LocatorAssertions { timeout?: number; }): Promise; + /** + * Asserts that the target element matches the given [accessibility snapshot](https://playwright.dev/docs/aria-snapshots). + * + * **Usage** + * + * ```js + * await expect(page.locator('body')).toMatchAriaSnapshot(); + * await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'snapshot' }); + * await expect(page.locator('body')).toMatchAriaSnapshot({ path: '/path/to/snapshot.yml' }); + * ``` + * + * @param options + */ + toMatchAriaSnapshot(options?: { + /** + * Name of the snapshot to store in the snapshot folder corresponding to this test. Generates ordinal name if not + * specified. + */ + name?: string; + + /** + * Path to the YAML snapshot file. + */ + path?: string; + + /** + * Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`. + */ + timeout?: number; + }): Promise; + /** * Asserts that the target element matches the given [accessibility snapshot](https://playwright.dev/docs/aria-snapshots). * diff --git a/tests/playwright-test/aria-snapshot-file.spec.ts b/tests/playwright-test/aria-snapshot-file.spec.ts new file mode 100644 index 0000000000..c05ae3897d --- /dev/null +++ b/tests/playwright-test/aria-snapshot-file.spec.ts @@ -0,0 +1,172 @@ +/** + * Copyright Microsoft Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { test, expect } from './playwright-test-fixtures'; +import fs from 'fs'; +import path from 'path'; + +test.describe.configure({ mode: 'parallel' }); + +test('should match snapshot with name', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + export default { + snapshotPathTemplate: '__snapshots__/{testFilePath}/{arg}{ext}', + }; + `, + '__snapshots__/a.spec.ts/test.yml': ` + - heading "hello world" + `, + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('test', async ({ page }) => { + await page.setContent(\`

hello world

\`); + await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test.yml' }); + }); + ` + }); + + expect(result.exitCode).toBe(0); +}); + +test('should match snapshot with path', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + 'test.yml': ` + - heading "hello world" + `, + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + import path from 'path'; + test('test', async ({ page }) => { + await page.setContent(\`

hello world

\`); + await expect(page.locator('body')).toMatchAriaSnapshot({ path: path.resolve(__dirname, 'test.yml') }); + }); + ` + }); + + expect(result.exitCode).toBe(0); +}); + +test('should generate multiple missing', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + export default { + snapshotPathTemplate: '__snapshots__/{testFilePath}/{arg}{ext}', + }; + `, + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('test', async ({ page }) => { + await page.setContent(\`

hello world

\`); + await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test-1.yml' }); + await page.setContent(\`

hello world 2

\`); + await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test-2.yml' }); + }); + ` + }); + + expect(result.exitCode).toBe(1); + expect(result.output).toContain(`A snapshot doesn't exist at __snapshots__${path.sep}a.spec.ts${path.sep}test-1.yml, writing actual`); + expect(result.output).toContain(`A snapshot doesn't exist at __snapshots__${path.sep}a.spec.ts${path.sep}test-2.yml, writing actual`); + const snapshot1 = await fs.promises.readFile(testInfo.outputPath('__snapshots__/a.spec.ts/test-1.yml'), 'utf8'); + expect(snapshot1).toBe('- heading "hello world" [level=1]'); + const snapshot2 = await fs.promises.readFile(testInfo.outputPath('__snapshots__/a.spec.ts/test-2.yml'), 'utf8'); + expect(snapshot2).toBe('- heading "hello world 2" [level=1]'); +}); + +test('should rebaseline all', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + export default { + snapshotPathTemplate: '__snapshots__/{testFilePath}/{arg}{ext}', + }; + `, + '__snapshots__/a.spec.ts/test-1.yml': ` + - heading "foo" + `, + '__snapshots__/a.spec.ts/test-2.yml': ` + - heading "bar" + `, + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('test', async ({ page }) => { + await page.setContent(\`

hello world

\`); + await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test-1.yml' }); + await page.setContent(\`

hello world 2

\`); + await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test-2.yml' }); + }); + ` + }, { 'update-snapshots': 'all' }); + + expect(result.exitCode).toBe(0); + expect(result.output).toContain(`A snapshot is generated at __snapshots__${path.sep}a.spec.ts${path.sep}test-1.yml`); + expect(result.output).toContain(`A snapshot is generated at __snapshots__${path.sep}a.spec.ts${path.sep}test-2.yml`); + const snapshot1 = await fs.promises.readFile(testInfo.outputPath('__snapshots__/a.spec.ts/test-1.yml'), 'utf8'); + expect(snapshot1).toBe('- heading "hello world" [level=1]'); + const snapshot2 = await fs.promises.readFile(testInfo.outputPath('__snapshots__/a.spec.ts/test-2.yml'), 'utf8'); + expect(snapshot2).toBe('- heading "hello world 2" [level=1]'); +}); + +test('should not rebaseline matching', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + export default { + snapshotPathTemplate: '__snapshots__/{testFilePath}/{arg}{ext}', + }; + `, + '__snapshots__/a.spec.ts/test.yml': ` + - heading "hello world" + `, + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('test', async ({ page }) => { + await page.setContent(\`

hello world

\`); + await expect(page.locator('body')).toMatchAriaSnapshot({ name: 'test.yml' }); + }); + ` + }, { 'update-snapshots': 'changed' }); + + expect(result.exitCode).toBe(0); + const snapshot1 = await fs.promises.readFile(testInfo.outputPath('__snapshots__/a.spec.ts/test.yml'), 'utf8'); + expect(snapshot1.trim()).toBe('- heading "hello world"'); +}); + +test('should generate snapshot name', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + export default { + snapshotPathTemplate: '__snapshots__/{testFilePath}/{arg}{ext}', + }; + `, + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('test name', async ({ page }) => { + await page.setContent(\`

hello world

\`); + await expect(page.locator('body')).toMatchAriaSnapshot(); + await page.setContent(\`

hello world 2

\`); + await expect(page.locator('body')).toMatchAriaSnapshot(); + }); + ` + }); + + expect(result.exitCode).toBe(1); + expect(result.output).toContain(`A snapshot doesn't exist at __snapshots__${path.sep}a.spec.ts${path.sep}test-name-1.yml, writing actual`); + expect(result.output).toContain(`A snapshot doesn't exist at __snapshots__${path.sep}a.spec.ts${path.sep}test-name-2.yml, writing actual`); + const snapshot1 = await fs.promises.readFile(testInfo.outputPath('__snapshots__/a.spec.ts/test-name-1.yml'), 'utf8'); + expect(snapshot1).toBe('- heading "hello world" [level=1]'); + const snapshot2 = await fs.promises.readFile(testInfo.outputPath('__snapshots__/a.spec.ts/test-name-2.yml'), 'utf8'); + expect(snapshot2).toBe('- heading "hello world 2" [level=1]'); +}); diff --git a/tests/playwright-test/update-aria-snapshot.spec.ts b/tests/playwright-test/update-aria-snapshot.spec.ts index 6a3d10eb43..15afff31e2 100644 --- a/tests/playwright-test/update-aria-snapshot.spec.ts +++ b/tests/playwright-test/update-aria-snapshot.spec.ts @@ -116,6 +116,55 @@ test('should update missing snapshots', async ({ runInlineTest }, testInfo) => { expect(result2.exitCode).toBe(0); }); +test('should update multiple missing snapshots', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + '.git/marker': '', + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('test', async ({ page }) => { + await page.setContent(\`

hello

\`); + await expect(page.locator('body')).toMatchAriaSnapshot(\`\`); + await expect(page.locator('body')).toMatchAriaSnapshot(\`\`); + }); + ` + }); + + expect(result.exitCode).toBe(0); + + expect(stripAnsi(result.output).replace(/\\/g, '/')).toContain(`New baselines created for: + + a.spec.ts + + git apply test-results/rebaselines.patch +`); + + const patchPath = testInfo.outputPath('test-results/rebaselines.patch'); + const data = fs.readFileSync(patchPath, 'utf-8'); + expect(trimPatch(data)).toBe(`diff --git a/a.spec.ts b/a.spec.ts +--- a/a.spec.ts ++++ b/a.spec.ts +@@ -2,7 +2,11 @@ + import { test, expect } from '@playwright/test'; + test('test', async ({ page }) => { + await page.setContent(\`

hello

\`); +- await expect(page.locator('body')).toMatchAriaSnapshot(\`\`); +- await expect(page.locator('body')).toMatchAriaSnapshot(\`\`); ++ await expect(page.locator('body')).toMatchAriaSnapshot(\` ++ - heading "hello" [level=1] ++ \`); ++ await expect(page.locator('body')).toMatchAriaSnapshot(\` ++ - heading "hello" [level=1] ++ \`); + }); + +\\ No newline at end of file +`); + + execSync(`patch -p1 < ${patchPath}`, { cwd: testInfo.outputPath() }); + const result2 = await runInlineTest({}); + expect(result2.exitCode).toBe(0); +}); + test('should generate baseline with regex', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ '.git/marker': '', From 54c595c7ed3f84d93dbef521d6c90b9c41c1e00c Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 10 Dec 2024 11:50:51 -0800 Subject: [PATCH 35/85] docs: add examples for clock.install() followed by pauseAt() (#33937) --- docs/src/api/class-clock.md | 35 +++++++++++++++++++++++ packages/playwright-core/types/types.d.ts | 13 +++++++++ 2 files changed, 48 insertions(+) diff --git a/docs/src/api/class-clock.md b/docs/src/api/class-clock.md index 38ca329769..e5610b4242 100644 --- a/docs/src/api/class-clock.md +++ b/docs/src/api/class-clock.md @@ -161,6 +161,41 @@ await page.Clock.PauseAtAsync(DateTime.Parse("2020-02-02")); await page.Clock.PauseAtAsync("2020-02-02"); ``` +For best results, install the clock before navigating the page and set it to a time slightly before the intended test time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. Once the page has fully loaded, you can safely use [`method: Clock.pauseAt`] to pause the clock. + +```js +// Initialize clock with some time before the test time and let the page load +// naturally. `Date.now` will progress as the timers fire. +await page.clock.install({ time: new Date('2024-12-10T08:00:00') }); +await page.goto('http://localhost:3333'); +await page.clock.pauseAt(new Date('2024-12-10T10:00:00')); +``` + +```python async +# Initialize clock with some time before the test time and let the page load +# naturally. `Date.now` will progress as the timers fire. +await page.clock.install(time=datetime.datetime(2024, 12, 10, 8, 0, 0)) +await page.goto("http://localhost:3333") +await page.clock.pause_at(datetime.datetime(2024, 12, 10, 10, 0, 0)) +``` + +```python sync +# Initialize clock with some time before the test time and let the page load +# naturally. `Date.now` will progress as the timers fire. +page.clock.install(time=datetime.datetime(2024, 12, 10, 8, 0, 0)) +page.goto("http://localhost:3333") +page.clock.pause_at(datetime.datetime(2024, 12, 10, 10, 0, 0)) +``` + +```java +// Initialize clock with some time before the test time and let the page load +// naturally. `Date.now` will progress as the timers fire. +SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss"); +page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-12-10T08:00:00"))); +page.navigate("http://localhost:3333"); +page.clock().pauseAt(format.parse("2024-12-10T10:00:00")); +``` + ### param: Clock.pauseAt.time * langs: js, java * since: v1.45 diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 9d29183992..8d2077c4d0 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -18590,6 +18590,19 @@ export interface Clock { * await page.clock.pauseAt('2020-02-02'); * ``` * + * For best results, install the clock before navigating the page and set it to a time slightly before the intended + * test time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. + * Once the page has fully loaded, you can safely use + * [clock.pauseAt(time)](https://playwright.dev/docs/api/class-clock#clock-pause-at) to pause the clock. + * + * ```js + * // Initialize clock with some time before the test time and let the page load + * // naturally. `Date.now` will progress as the timers fire. + * await page.clock.install({ time: new Date('2024-12-10T08:00:00') }); + * await page.goto('http://localhost:3333'); + * await page.clock.pauseAt(new Date('2024-12-10T10:00:00')); + * ``` + * * @param time Time to pause at. */ pauseAt(time: number|string|Date): Promise; From 599b09c1c0f9aa0a6786540b43d1c4af2c9444cf Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 10 Dec 2024 14:02:13 -0800 Subject: [PATCH 36/85] docs: sharding per file with multiple projects (#33939) --- docs/src/test-sharding-js.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/test-sharding-js.md b/docs/src/test-sharding-js.md index d4b474ca9b..3b0f55e333 100644 --- a/docs/src/test-sharding-js.md +++ b/docs/src/test-sharding-js.md @@ -37,7 +37,7 @@ When `fullyParallel: true` is enabled, Playwright Test runs individual tests in **Sharding without fullyParallel** -Without the fullyParallel setting, Playwright Test defaults to file-level granularity, meaning entire test files are assigned to shards. In this case, the number of tests per file can greatly influence shard distribution. If your test files are not evenly sized (i.e., some files contain many more tests than others), certain shards may end up running significantly more tests, while others may run fewer or even none. +Without the fullyParallel setting, Playwright Test defaults to file-level granularity, meaning entire test files are assigned to shards (note that the same file may be assigned to different shards across different projects). In this case, the number of tests per file can greatly influence shard distribution. If your test files are not evenly sized (i.e., some files contain many more tests than others), certain shards may end up running significantly more tests, while others may run fewer or even none. **Key Takeaways:** From acf110722003809a7a96d987f365b5d555aae17b Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 10 Dec 2024 14:04:18 -0800 Subject: [PATCH 37/85] chore: ignore checkbox/radio value in aria (#33941) --- .../src/server/injected/ariaSnapshot.ts | 6 ++++-- tests/page/page-aria-snapshot.spec.ts | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/server/injected/ariaSnapshot.ts b/packages/playwright-core/src/server/injected/ariaSnapshot.ts index d541646f0e..84537cddd7 100644 --- a/packages/playwright-core/src/server/injected/ariaSnapshot.ts +++ b/packages/playwright-core/src/server/injected/ariaSnapshot.ts @@ -158,8 +158,10 @@ function toAriaNode(element: Element): AriaNode | null { if (roleUtils.kAriaSelectedRoles.includes(role)) result.selected = roleUtils.getAriaSelected(element); - if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) - result.children = [element.value]; + if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) { + if (element.type !== 'checkbox' && element.type !== 'radio') + result.children = [element.value]; + } return result; } diff --git a/tests/page/page-aria-snapshot.spec.ts b/tests/page/page-aria-snapshot.spec.ts index 81e1beadfa..77dc45a01e 100644 --- a/tests/page/page-aria-snapshot.spec.ts +++ b/tests/page/page-aria-snapshot.spec.ts @@ -421,6 +421,18 @@ it('should treat input value as text in templates', async ({ page }) => { `); }); +it('should not use on as checkbox value', async ({ page }) => { + await page.setContent(` + + + `); + + await checkAndMatchSnapshot(page.locator('body'), ` + - checkbox + - radio + `); +}); + it('should respect aria-owns', async ({ page }) => { await page.setContent(` From 4745f64bc0bf9f27b537393a28af318e8a72b685 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:29:30 -0800 Subject: [PATCH 38/85] feat(webkit): roll to r2118 (#33938) --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index da7775185d..ccbdd8e380 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -33,7 +33,7 @@ }, { "name": "webkit", - "revision": "2117", + "revision": "2118", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", From 4bcf505e19477f861009b05acd34221279245351 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 10 Dec 2024 16:03:33 -0800 Subject: [PATCH 39/85] chore: prefer generating role with text to css with text (#33942) --- .../src/server/injected/selectorGenerator.ts | 45 +++++++++++-------- tests/library/selector-generator.spec.ts | 21 ++++++++- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/packages/playwright-core/src/server/injected/selectorGenerator.ts b/packages/playwright-core/src/server/injected/selectorGenerator.ts index 7aa22e6f0c..4b8f704944 100644 --- a/packages/playwright-core/src/server/injected/selectorGenerator.ts +++ b/packages/playwright-core/src/server/injected/selectorGenerator.ts @@ -268,7 +268,7 @@ function buildNoTextCandidates(injectedScript: InjectedScript, element: Element, if (input.placeholder) { candidates.push({ engine: 'internal:attr', selector: `[placeholder=${escapeForAttributeSelector(input.placeholder, true)}]`, score: kPlaceholderScoreExact }); for (const alternative of suitableTextAlternatives(input.placeholder)) - candidates.push({ engine: 'internal:attr', selector: `[placeholder=${escapeForAttributeSelector(alternative.text, false)}]`, score: kPlaceholderScore - alternative.scoreBouns }); + candidates.push({ engine: 'internal:attr', selector: `[placeholder=${escapeForAttributeSelector(alternative.text, false)}]`, score: kPlaceholderScore - alternative.scoreBonus }); } } @@ -277,7 +277,7 @@ function buildNoTextCandidates(injectedScript: InjectedScript, element: Element, const labelText = label.normalized; candidates.push({ engine: 'internal:label', selector: escapeForTextSelector(labelText, true), score: kLabelScoreExact }); for (const alternative of suitableTextAlternatives(labelText)) - candidates.push({ engine: 'internal:label', selector: escapeForTextSelector(alternative.text, false), score: kLabelScore - alternative.scoreBouns }); + candidates.push({ engine: 'internal:label', selector: escapeForTextSelector(alternative.text, false), score: kLabelScore - alternative.scoreBonus }); } const ariaRole = getAriaRole(element); @@ -308,28 +308,28 @@ function buildTextCandidates(injectedScript: InjectedScript, element: Element, i if (title) { candidates.push([{ engine: 'internal:attr', selector: `[title=${escapeForAttributeSelector(title, true)}]`, score: kTitleScoreExact }]); for (const alternative of suitableTextAlternatives(title)) - candidates.push([{ engine: 'internal:attr', selector: `[title=${escapeForAttributeSelector(alternative.text, false)}]`, score: kTitleScore - alternative.scoreBouns }]); + candidates.push([{ engine: 'internal:attr', selector: `[title=${escapeForAttributeSelector(alternative.text, false)}]`, score: kTitleScore - alternative.scoreBonus }]); } const alt = element.getAttribute('alt'); if (alt && ['APPLET', 'AREA', 'IMG', 'INPUT'].includes(element.nodeName)) { candidates.push([{ engine: 'internal:attr', selector: `[alt=${escapeForAttributeSelector(alt, true)}]`, score: kAltTextScoreExact }]); for (const alternative of suitableTextAlternatives(alt)) - candidates.push([{ engine: 'internal:attr', selector: `[alt=${escapeForAttributeSelector(alternative.text, false)}]`, score: kAltTextScore - alternative.scoreBouns }]); + candidates.push([{ engine: 'internal:attr', selector: `[alt=${escapeForAttributeSelector(alternative.text, false)}]`, score: kAltTextScore - alternative.scoreBonus }]); } const text = elementText(injectedScript._evaluator._cacheText, element).normalized; + const textAlternatives = text ? suitableTextAlternatives(text) : []; if (text) { - const alternatives = suitableTextAlternatives(text); if (isTargetNode) { if (text.length <= 80) candidates.push([{ engine: 'internal:text', selector: escapeForTextSelector(text, true), score: kTextScoreExact }]); - for (const alternative of alternatives) - candidates.push([{ engine: 'internal:text', selector: escapeForTextSelector(alternative.text, false), score: kTextScore - alternative.scoreBouns }]); + for (const alternative of textAlternatives) + candidates.push([{ engine: 'internal:text', selector: escapeForTextSelector(alternative.text, false), score: kTextScore - alternative.scoreBonus }]); } const cssToken: SelectorToken = { engine: 'css', selector: cssEscape(element.nodeName.toLowerCase()), score: kCSSTagNameScore }; - for (const alternative of alternatives) - candidates.push([cssToken, { engine: 'internal:has-text', selector: escapeForTextSelector(alternative.text, false), score: kTextScore - alternative.scoreBouns }]); + for (const alternative of textAlternatives) + candidates.push([cssToken, { engine: 'internal:has-text', selector: escapeForTextSelector(alternative.text, false), score: kTextScore - alternative.scoreBonus }]); if (text.length <= 80) { const re = new RegExp('^' + escapeRegExp(text) + '$'); candidates.push([cssToken, { engine: 'internal:has-text', selector: escapeForTextSelector(re, false), score: kTextScoreRegex }]); @@ -340,9 +340,18 @@ function buildTextCandidates(injectedScript: InjectedScript, element: Element, i if (ariaRole && !['none', 'presentation'].includes(ariaRole)) { const ariaName = getElementAccessibleName(element, false); if (ariaName) { - candidates.push([{ engine: 'internal:role', selector: `${ariaRole}[name=${escapeForAttributeSelector(ariaName, true)}]`, score: kRoleWithNameScoreExact }]); + const roleToken = { engine: 'internal:role', selector: `${ariaRole}[name=${escapeForAttributeSelector(ariaName, true)}]`, score: kRoleWithNameScoreExact }; + candidates.push([roleToken]); for (const alternative of suitableTextAlternatives(ariaName)) - candidates.push([{ engine: 'internal:role', selector: `${ariaRole}[name=${escapeForAttributeSelector(alternative.text, false)}]`, score: kRoleWithNameScore - alternative.scoreBouns }]); + candidates.push([{ engine: 'internal:role', selector: `${ariaRole}[name=${escapeForAttributeSelector(alternative.text, false)}]`, score: kRoleWithNameScore - alternative.scoreBonus }]); + } else { + const roleToken = { engine: 'internal:role', selector: `${ariaRole}`, score: kRoleWithoutNameScore }; + for (const alternative of textAlternatives) + candidates.push([roleToken, { engine: 'internal:has-text', selector: escapeForTextSelector(alternative.text, false), score: kTextScore - alternative.scoreBonus }]); + if (text.length <= 80) { + const re = new RegExp('^' + escapeRegExp(text) + '$'); + candidates.push([roleToken, { engine: 'internal:has-text', selector: escapeForTextSelector(re, false), score: kTextScoreRegex }]); + } } } @@ -527,14 +536,14 @@ function trimWordBoundary(text: string, maxLength: number) { } function suitableTextAlternatives(text: string) { - let result: { text: string, scoreBouns: number }[] = []; + let result: { text: string, scoreBonus: number }[] = []; { const match = text.match(/^([\d.,]+)[^.,\w]/); const leadingNumberLength = match ? match[1].length : 0; if (leadingNumberLength) { const alt = trimWordBoundary(text.substring(leadingNumberLength).trimStart(), 80); - result.push({ text: alt, scoreBouns: alt.length <= 30 ? 2 : 1 }); + result.push({ text: alt, scoreBonus: alt.length <= 30 ? 2 : 1 }); } } @@ -543,20 +552,20 @@ function suitableTextAlternatives(text: string) { const trailingNumberLength = match ? match[1].length : 0; if (trailingNumberLength) { const alt = trimWordBoundary(text.substring(0, text.length - trailingNumberLength).trimEnd(), 80); - result.push({ text: alt, scoreBouns: alt.length <= 30 ? 2 : 1 }); + result.push({ text: alt, scoreBonus: alt.length <= 30 ? 2 : 1 }); } } if (text.length <= 30) { - result.push({ text, scoreBouns: 0 }); + result.push({ text, scoreBonus: 0 }); } else { - result.push({ text: trimWordBoundary(text, 80), scoreBouns: 0 }); - result.push({ text: trimWordBoundary(text, 30), scoreBouns: 1 }); + result.push({ text: trimWordBoundary(text, 80), scoreBonus: 0 }); + result.push({ text: trimWordBoundary(text, 30), scoreBonus: 1 }); } result = result.filter(r => r.text); if (!result.length) - result.push({ text: text.substring(0, 80), scoreBouns: 0 }); + result.push({ text: text.substring(0, 80), scoreBonus: 0 }); return result; } diff --git a/tests/library/selector-generator.spec.ts b/tests/library/selector-generator.spec.ts index e9df1afc6e..c2e074c49f 100644 --- a/tests/library/selector-generator.spec.ts +++ b/tests/library/selector-generator.spec.ts @@ -309,7 +309,7 @@ it.describe('selector generator', () => { expect(await generate(page, 'input[mark="1"]')).toBe('internal:role=textbox >> nth=1'); }); - it.describe('should prioritise attributes correctly', () => { + it.describe('should prioritize attributes correctly', () => { it('role', async ({ page }) => { await page.setContent(``); expect(await generate(page, 'input')).toBe('internal:role=textbox'); @@ -596,4 +596,23 @@ it.describe('selector generator', () => { `span >> nth=1`, ]); }); + + it('should prefer role with hasText to css with hasText', async ({ page }) => { + await page.setContent(` +
    +
  • + + buy flowers +
  • +
  • + + sell milk +
  • +
+ `); + expect(await generateMultiple(page, 'input')).toEqual([ + `internal:role=listitem >> internal:has-text=\"buy flowers\"i >> internal:label=\"Toggle Todo\"i`, + `internal:label=\"Toggle Todo\"i >> nth=0`, + ]); + }); }); From a14d9750b314c26d72daa9f33598ad9d66d3e61c Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Tue, 10 Dec 2024 16:18:41 -0800 Subject: [PATCH 40/85] docs: #33837 Clarify that clock.install must precede clock operations (#33901) --- docs/src/clock.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/clock.md b/docs/src/clock.md index 2f846e4fd3..44582f450b 100644 --- a/docs/src/clock.md +++ b/docs/src/clock.md @@ -34,6 +34,10 @@ The recommended approach is to use `setFixedTime` to set the time to a specific - `Event.timeStamp` ::: +:::warning +If you call `install` at any point in your test, the call _MUST_ occur before any other clock related calls (see note above for list). Calling these methods out of order will result in undefined behavior. For example, you cannot call `setInterval`, followed by `install`, then `clearInterval`, as `install` overrides the native definition of the clock functions. +::: + ## Test with predefined time Often you only need to fake `Date.now` while keeping the timers going. From ed2be67e47f7f5c66eaa2f382fc41b010978d82f Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Wed, 11 Dec 2024 04:25:52 -0800 Subject: [PATCH 41/85] chore(trace viewer): support HMR (#33609) --- .../src/server/trace/viewer/traceViewer.ts | 8 +++++++- packages/trace-viewer/src/sw/main.ts | 6 ++---- packages/trace-viewer/src/ui/snapshotTab.tsx | 3 ++- packages/trace-viewer/src/ui/uiModeView.tsx | 4 +--- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts index b8dc3e5314..f852f6c996 100644 --- a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts +++ b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts @@ -125,7 +125,13 @@ export async function installRootRedirect(server: HttpServer, traceUrls: string[ for (const reporter of options.reporter || []) params.append('reporter', reporter); - const urlPath = `./trace/${options.webApp || 'index.html'}?${params.toString()}`; + let baseUrl = '.'; + if (process.env.PW_HMR) { + baseUrl = 'http://localhost:44223'; // port is hardcoded in build.js + params.set('server', server.urlPrefix('precise')); + } + + const urlPath = `${baseUrl}/trace/${options.webApp || 'index.html'}?${params.toString()}`; server.routePath('/', (_, response) => { response.statusCode = 302; response.setHeader('Location', urlPath); diff --git a/packages/trace-viewer/src/sw/main.ts b/packages/trace-viewer/src/sw/main.ts index 4d01ef2a61..cda3ba8cad 100644 --- a/packages/trace-viewer/src/sw/main.ts +++ b/packages/trace-viewer/src/sw/main.ts @@ -43,10 +43,8 @@ async function loadTrace(traceUrl: string, traceFileName: string | null, client: const clientId = client?.id ?? ''; let data = clientIdToTraceUrls.get(clientId); if (!data) { - let traceViewerServerBaseUrl = new URL('../', client?.url ?? self.registration.scope); - if (traceViewerServerBaseUrl.searchParams.has('server')) - traceViewerServerBaseUrl = new URL(traceViewerServerBaseUrl.searchParams.get('server')!, traceViewerServerBaseUrl); - + const clientURL = new URL(client?.url ?? self.registration.scope); + const traceViewerServerBaseUrl = new URL(clientURL.searchParams.get('server') ?? '../', clientURL); data = { limit, traceUrls: new Set(), traceViewerServer: new TraceViewerServer(traceViewerServerBaseUrl) }; clientIdToTraceUrls.set(clientId, data); } diff --git a/packages/trace-viewer/src/ui/snapshotTab.tsx b/packages/trace-viewer/src/ui/snapshotTab.tsx index 8383890a2e..995e2d173f 100644 --- a/packages/trace-viewer/src/ui/snapshotTab.tsx +++ b/packages/trace-viewer/src/ui/snapshotTab.tsx @@ -344,7 +344,8 @@ export function extendSnapshot(snapshot: Snapshot): SnapshotUrls { const popoutParams = new URLSearchParams(); popoutParams.set('r', snapshotUrl); - popoutParams.set('server', serverParam ?? ''); + if (serverParam) + popoutParams.set('server', serverParam); popoutParams.set('trace', context(snapshot.action).traceUrl); if (snapshot.point) { popoutParams.set('pointX', String(snapshot.point.x)); diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index be0dd8f55f..aa15e5a0a5 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -47,9 +47,7 @@ const xtermDataSource: XtermDataSource = { }; const searchParams = new URLSearchParams(window.location.search); -let testServerBaseUrl = new URL('../', window.location.href); -if (testServerBaseUrl.searchParams.has('server')) - testServerBaseUrl = new URL(testServerBaseUrl.searchParams.get('server')!, testServerBaseUrl); +const testServerBaseUrl = new URL(searchParams.get('server') ?? '../', window.location.href); const wsURL = new URL(searchParams.get('ws')!, testServerBaseUrl); wsURL.protocol = (wsURL.protocol === 'https:' ? 'wss:' : 'ws:'); const queryParams = { From 6d424875e3ab248c66e43eda54f1c3105e456b7a Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Wed, 11 Dec 2024 06:16:21 -0800 Subject: [PATCH 42/85] fix(html): remove filter clear button (#33754) --- packages/html-reporter/src/headerView.tsx | 6 ++++-- packages/html-reporter/src/links.tsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/html-reporter/src/headerView.tsx b/packages/html-reporter/src/headerView.tsx index 01f92f1ecb..8384f9e31d 100644 --- a/packages/html-reporter/src/headerView.tsx +++ b/packages/html-reporter/src/headerView.tsx @@ -49,12 +49,14 @@ export const HeaderView: React.FC { event.preventDefault(); - navigate(`#?q=${filterText ? encodeURIComponent(filterText) : ''}`); + const url = new URL(window.location.href); + url.hash = filterText ? '?' + new URLSearchParams({ q: filterText }) : ''; + navigate(url); } }> {icons.search()} {/* Use navigationId to reset defaultValue */} - { + { setFilterText(e.target.value); }}> diff --git a/packages/html-reporter/src/links.tsx b/packages/html-reporter/src/links.tsx index 4beb7b65e0..1e5cad48c1 100644 --- a/packages/html-reporter/src/links.tsx +++ b/packages/html-reporter/src/links.tsx @@ -23,7 +23,7 @@ import './links.css'; import { linkifyText } from '@web/renderUtils'; import { clsx } from '@web/uiUtils'; -export function navigate(href: string) { +export function navigate(href: string | URL) { window.history.pushState({}, '', href); const navEvent = new PopStateEvent('popstate'); window.dispatchEvent(navEvent); From 5a1bae0f190c09871bb4982857b1964e4c6adbc7 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:30:17 -0800 Subject: [PATCH 43/85] feat(chromium-tip-of-tree): roll to r1285 (#33945) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index ccbdd8e380..99b7b70338 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,9 +15,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1284", + "revision": "1285", "installByDefault": false, - "browserVersion": "133.0.6878.0" + "browserVersion": "133.0.6887.0" }, { "name": "firefox", From 856704a1768f35890d0c386ad4f0630c963d8978 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 11 Dec 2024 10:35:14 -0800 Subject: [PATCH 44/85] chore: run server with headless shell for connect tests (#33957) --- tests/config/remoteServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/config/remoteServer.ts b/tests/config/remoteServer.ts index 04305f31a4..6d9710fd44 100644 --- a/tests/config/remoteServer.ts +++ b/tests/config/remoteServer.ts @@ -92,7 +92,7 @@ export class RemoteServer implements PlaywrightServer { handleSIGINT: true, handleSIGTERM: true, handleSIGHUP: true, - executablePath: browserOptions.channel ? undefined : browserOptions.executablePath || browserType.executablePath(), + executablePath: browserOptions.channel ? undefined : browserOptions.executablePath, logger: undefined, }; const options = { From 9e683d798fdffcc1d943a881b47436d6b375692e Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 11 Dec 2024 13:06:37 -0800 Subject: [PATCH 45/85] docs: clarify setDefaultTimeout with 0 value (#33959) --- docs/src/api/class-browsercontext.md | 2 +- docs/src/api/class-page.md | 2 +- packages/playwright-core/types/types.d.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md index 121ce1fdf6..85cbc4f62a 100644 --- a/docs/src/api/class-browsercontext.md +++ b/docs/src/api/class-browsercontext.md @@ -1412,7 +1412,7 @@ This setting will change the default maximum time for all the methods accepting * since: v1.8 - `timeout` <[float]> -Maximum time in milliseconds +Maximum time in milliseconds. Pass `0` to disable timeout. ## async method: BrowserContext.setExtraHTTPHeaders * since: v1.8 diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index f44bd18696..ec58c027b3 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -3970,7 +3970,7 @@ This setting will change the default maximum time for all the methods accepting * since: v1.8 - `timeout` <[float]> -Maximum time in milliseconds +Maximum time in milliseconds. Pass `0` to disable timeout. ## async method: Page.setExtraHTTPHeaders * since: v1.8 diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 8d2077c4d0..cb83bb4ccd 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -4294,7 +4294,7 @@ export interface Page { * takes priority over * [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout). * - * @param timeout Maximum time in milliseconds + * @param timeout Maximum time in milliseconds. Pass `0` to disable timeout. */ setDefaultTimeout(timeout: number): void; @@ -9193,7 +9193,7 @@ export interface BrowserContext { * take priority over * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout). * - * @param timeout Maximum time in milliseconds + * @param timeout Maximum time in milliseconds. Pass `0` to disable timeout. */ setDefaultTimeout(timeout: number): void; From 22e58aa08484d7b4ff793faa247c58e027bcd3c6 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 11 Dec 2024 13:06:48 -0800 Subject: [PATCH 46/85] docs: clarify --only-shell option (#33961) --- docs/src/browsers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/browsers.md b/docs/src/browsers.md index 83ea0f60c3..90c0b2850b 100644 --- a/docs/src/browsers.md +++ b/docs/src/browsers.md @@ -342,7 +342,7 @@ Playwright ships a regular Chromium build for headed operations and a separate [ #### Optimize download size on CI -If you are only running tests in headless mode, for example on CI, you can avoid downloading a regular version of Chromium by passing `--only-shell` during installation. +If you are only running tests in headless shell (i.e. the `channel` option is not specified), for example on CI, you can avoid downloading the full Chromium browser by passing `--only-shell` during installation. ```bash js # only running tests headlessly From e4e562cac5ea65c5362b684177c3ecf35eeb50b3 Mon Sep 17 00:00:00 2001 From: Bill Collins Date: Thu, 12 Dec 2024 01:09:19 +0000 Subject: [PATCH 47/85] fix(types): Global JSX namespace is deprecated (#33949) --- packages/playwright-ct-react/hooks.d.ts | 4 +++- packages/playwright-ct-react/index.d.ts | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/playwright-ct-react/hooks.d.ts b/packages/playwright-ct-react/hooks.d.ts index 1093f1a3a3..f05ad1ffdf 100644 --- a/packages/playwright-ct-react/hooks.d.ts +++ b/packages/playwright-ct-react/hooks.d.ts @@ -14,8 +14,10 @@ * limitations under the License. */ +import type React from 'react'; + export declare function beforeMount( - callback: (params: { hooksConfig?: HooksConfig; App: () => JSX.Element }) => Promise + callback: (params: { hooksConfig?: HooksConfig; App: () => React.JSX.Element }) => Promise ): void; export declare function afterMount( callback: (params: { hooksConfig?: HooksConfig }) => Promise diff --git a/packages/playwright-ct-react/index.d.ts b/packages/playwright-ct-react/index.d.ts index c5e6d6d2da..d7000c5c85 100644 --- a/packages/playwright-ct-react/index.d.ts +++ b/packages/playwright-ct-react/index.d.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import type React from 'react'; + import type { TestType, Locator } from '@playwright/experimental-ct-core'; export interface MountOptions { @@ -22,12 +24,12 @@ export interface MountOptions { export interface MountResult extends Locator { unmount(): Promise; - update(component: JSX.Element): Promise; + update(component: React.JSX.Element): Promise; } export const test: TestType<{ mount( - component: JSX.Element, + component: React.JSX.Element, options?: MountOptions ): Promise; }>; From b12ede48fc12c242b1f00ae5fb1d8d9c3dffbe71 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 11 Dec 2024 17:49:16 -0800 Subject: [PATCH 48/85] test: fix html-reporter tests after #33754 (#33966) --- packages/html-reporter/src/headerView.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/html-reporter/src/headerView.spec.tsx b/packages/html-reporter/src/headerView.spec.tsx index f783a33c1d..202cd5fa7b 100644 --- a/packages/html-reporter/src/headerView.spec.tsx +++ b/packages/html-reporter/src/headerView.spec.tsx @@ -64,6 +64,6 @@ test('should toggle filters', async ({ page, mount }) => { await expect(page).toHaveURL(/#\?q=s:flaky/); await component.locator('a', { hasText: 'Skipped' }).click(); await expect(page).toHaveURL(/#\?q=s:skipped/); - await component.getByRole('searchbox').fill('annot:annotation type=annotation description'); + await component.getByRole('textbox').fill('annot:annotation type=annotation description'); expect(filters).toEqual(['', 's:passed', 's:failed', 's:flaky', 's:skipped', 'annot:annotation type=annotation description']); }); From 217a0e9003bbe8f8412a0a0e2c0b43ea79e67449 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 11 Dec 2024 17:52:52 -0800 Subject: [PATCH 49/85] chore: bump https/socks proxy-agent (#33965) --- .../playwright-core/ThirdPartyNotices.txt | 18 +++---- .../bundles/utils/package-lock.json | 54 +++++++++---------- .../bundles/utils/package.json | 4 +- 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/packages/playwright-core/ThirdPartyNotices.txt b/packages/playwright-core/ThirdPartyNotices.txt index 89a6418ab8..2dc33e8c8f 100644 --- a/packages/playwright-core/ThirdPartyNotices.txt +++ b/packages/playwright-core/ThirdPartyNotices.txt @@ -6,7 +6,7 @@ This project incorporates components from the projects listed below. The origina - @types/node@17.0.24 (https://github.com/DefinitelyTyped/DefinitelyTyped) - @types/yauzl@2.10.0 (https://github.com/DefinitelyTyped/DefinitelyTyped) -- agent-base@7.1.1 (https://github.com/TooTallNate/proxy-agents) +- agent-base@7.1.3 (https://github.com/TooTallNate/proxy-agents) - balanced-match@1.0.2 (https://github.com/juliangruber/balanced-match) - brace-expansion@1.1.11 (https://github.com/juliangruber/brace-expansion) - buffer-crc32@0.2.13 (https://github.com/brianloveswords/buffer-crc32) @@ -24,7 +24,7 @@ This project incorporates components from the projects listed below. The origina - fd-slicer@1.1.0 (https://github.com/andrewrk/node-fd-slicer) - get-stream@5.2.0 (https://github.com/sindresorhus/get-stream) - graceful-fs@4.2.10 (https://github.com/isaacs/node-graceful-fs) -- https-proxy-agent@7.0.5 (https://github.com/TooTallNate/proxy-agents) +- https-proxy-agent@7.0.6 (https://github.com/TooTallNate/proxy-agents) - ip-address@9.0.5 (https://github.com/beaugunderson/ip-address) - is-docker@2.2.1 (https://github.com/sindresorhus/is-docker) - is-wsl@2.2.0 (https://github.com/sindresorhus/is-wsl) @@ -43,7 +43,7 @@ This project incorporates components from the projects listed below. The origina - retry@0.12.0 (https://github.com/tim-kos/node-retry) - signal-exit@3.0.7 (https://github.com/tapjs/signal-exit) - smart-buffer@4.2.0 (https://github.com/JoshGlazebrook/smart-buffer) -- socks-proxy-agent@8.0.4 (https://github.com/TooTallNate/proxy-agents) +- socks-proxy-agent@8.0.5 (https://github.com/TooTallNate/proxy-agents) - socks@2.8.3 (https://github.com/JoshGlazebrook/socks) - sprintf-js@1.1.3 (https://github.com/alexei/sprintf.js) - stack-utils@2.0.5 (https://github.com/tapjs/stack-utils) @@ -105,7 +105,7 @@ MIT License ========================================= END OF @types/yauzl@2.10.0 AND INFORMATION -%% agent-base@7.1.1 NOTICES AND INFORMATION BEGIN HERE +%% agent-base@7.1.3 NOTICES AND INFORMATION BEGIN HERE ========================================= (The MIT License) @@ -130,7 +130,7 @@ 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. ========================================= -END OF agent-base@7.1.1 AND INFORMATION +END OF agent-base@7.1.3 AND INFORMATION %% balanced-match@1.0.2 NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -542,7 +542,7 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ========================================= END OF graceful-fs@4.2.10 AND INFORMATION -%% https-proxy-agent@7.0.5 NOTICES AND INFORMATION BEGIN HERE +%% https-proxy-agent@7.0.6 NOTICES AND INFORMATION BEGIN HERE ========================================= (The MIT License) @@ -567,7 +567,7 @@ 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. ========================================= -END OF https-proxy-agent@7.0.5 AND INFORMATION +END OF https-proxy-agent@7.0.6 AND INFORMATION %% ip-address@9.0.5 NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -1005,7 +1005,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF smart-buffer@4.2.0 AND INFORMATION -%% socks-proxy-agent@8.0.4 NOTICES AND INFORMATION BEGIN HERE +%% socks-proxy-agent@8.0.5 NOTICES AND INFORMATION BEGIN HERE ========================================= (The MIT License) @@ -1030,7 +1030,7 @@ 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. ========================================= -END OF socks-proxy-agent@8.0.4 AND INFORMATION +END OF socks-proxy-agent@8.0.5 AND INFORMATION %% socks@2.8.3 NOTICES AND INFORMATION BEGIN HERE ========================================= diff --git a/packages/playwright-core/bundles/utils/package-lock.json b/packages/playwright-core/bundles/utils/package-lock.json index 6d9b0562e3..44aabaca9d 100644 --- a/packages/playwright-core/bundles/utils/package-lock.json +++ b/packages/playwright-core/bundles/utils/package-lock.json @@ -14,7 +14,7 @@ "diff": "^7.0.0", "dotenv": "^16.4.5", "graceful-fs": "4.2.10", - "https-proxy-agent": "7.0.5", + "https-proxy-agent": "7.0.6", "jpeg-js": "0.4.4", "mime": "^3.0.0", "minimatch": "^3.1.2", @@ -24,7 +24,7 @@ "proxy-from-env": "1.1.0", "retry": "0.12.0", "signal-exit": "3.0.7", - "socks-proxy-agent": "8.0.4", + "socks-proxy-agent": "8.0.5", "stack-utils": "2.0.5", "ws": "8.17.1", "yaml": "^2.6.0" @@ -140,13 +140,10 @@ } }, "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, "engines": { "node": ">= 14" } @@ -244,12 +241,12 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -403,12 +400,12 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "license": "MIT", "dependencies": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" }, @@ -562,12 +559,9 @@ } }, "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "requires": { - "debug": "^4.3.4" - } + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==" }, "balanced-match": { "version": "1.0.2", @@ -632,11 +626,11 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "requires": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" } }, @@ -740,11 +734,11 @@ } }, "socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "requires": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } diff --git a/packages/playwright-core/bundles/utils/package.json b/packages/playwright-core/bundles/utils/package.json index 1900d423f5..a409cca037 100644 --- a/packages/playwright-core/bundles/utils/package.json +++ b/packages/playwright-core/bundles/utils/package.json @@ -15,7 +15,7 @@ "diff": "^7.0.0", "dotenv": "^16.4.5", "graceful-fs": "4.2.10", - "https-proxy-agent": "7.0.5", + "https-proxy-agent": "7.0.6", "jpeg-js": "0.4.4", "mime": "^3.0.0", "minimatch": "^3.1.2", @@ -25,7 +25,7 @@ "proxy-from-env": "1.1.0", "retry": "0.12.0", "signal-exit": "3.0.7", - "socks-proxy-agent": "8.0.4", + "socks-proxy-agent": "8.0.5", "stack-utils": "2.0.5", "ws": "8.17.1", "yaml": "^2.6.0" From 8d57b7543e1b065840889fd3dbdb4149744cffc9 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 11 Dec 2024 18:11:33 -0800 Subject: [PATCH 50/85] chore: introduce chromium-tip-of-tree-headless-shell (#33964) --- .github/workflows/tests_secondary.yml | 21 ++++++++ packages/playwright-core/browsers.json | 6 +++ .../src/server/registry/index.ts | 51 ++++++++++++++++++- tests/config/browserTest.ts | 2 +- tests/library/headful.spec.ts | 2 +- tests/library/launcher.spec.ts | 1 + utils/roll_browser.js | 4 ++ 7 files changed, 83 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests_secondary.yml b/.github/workflows/tests_secondary.yml index de9434d7df..5afd2f5491 100644 --- a/.github/workflows/tests_secondary.yml +++ b/.github/workflows/tests_secondary.yml @@ -234,6 +234,27 @@ jobs: env: PWTEST_CHANNEL: chromium-tip-of-tree + chromium_tot_headless_shell: + name: Chromium tip-of-tree headless-shell-${{ matrix.os }} + environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-20.04] + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/run-test + with: + browsers-to-install: chromium-tip-of-tree-headless-shell + command: npm run ctest + bot-name: "chromium-tip-of-tree-headless-shell-${{ matrix.os }}" + flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} + flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} + flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} + env: + PWTEST_CHANNEL: chromium-tip-of-tree-headless-shell + firefox_beta: name: Firefox Beta ${{ matrix.os }} environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 99b7b70338..f5ced4b17c 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -19,6 +19,12 @@ "installByDefault": false, "browserVersion": "133.0.6887.0" }, + { + "name": "chromium-tip-of-tree-headless-shell", + "revision": "1285", + "installByDefault": true, + "browserVersion": "133.0.6887.0" + }, { "name": "firefox", "revision": "1466", diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index cd2f2f1d32..6326824534 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -174,6 +174,35 @@ const DOWNLOAD_PATHS: Record = { 'mac15-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip', 'win64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-win64.zip', }, + 'chromium-tip-of-tree-headless-shell': { + '': undefined, + 'ubuntu18.04-x64': undefined, + 'ubuntu20.04-x64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip', + 'ubuntu22.04-x64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip', + 'ubuntu24.04-x64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip', + 'ubuntu18.04-arm64': undefined, + 'ubuntu20.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip', + 'ubuntu22.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip', + 'ubuntu24.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip', + 'debian11-x64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip', + 'debian11-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip', + 'debian12-x64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip', + 'debian12-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip', + 'mac10.13': undefined, + 'mac10.14': undefined, + 'mac10.15': undefined, + 'mac11': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip', + 'mac11-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip', + 'mac12': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip', + 'mac12-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip', + 'mac13': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip', + 'mac13-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip', + 'mac14': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip', + 'mac14-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip', + 'mac15': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip', + 'mac15-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip', + 'win64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-win64.zip', + }, 'firefox': { '': undefined, 'ubuntu18.04-x64': undefined, @@ -410,10 +439,10 @@ function readDescriptors(browsersJSON: BrowsersJSON): BrowsersJSONDescriptor[] { } export type BrowserName = 'chromium' | 'firefox' | 'webkit' | 'bidi'; -type InternalTool = 'ffmpeg' | 'firefox-beta' | 'chromium-tip-of-tree' | 'chromium-headless-shell' | 'android'; +type InternalTool = 'ffmpeg' | 'firefox-beta' | 'chromium-tip-of-tree' | 'chromium-headless-shell' | 'chromium-tip-of-tree-headless-shell' | 'android'; type BidiChannel = 'bidi-firefox-stable' | 'bidi-firefox-beta' | 'bidi-firefox-nightly' | 'bidi-chrome-canary' | 'bidi-chrome-stable' | 'bidi-chromium'; type ChromiumChannel = 'chrome' | 'chrome-beta' | 'chrome-dev' | 'chrome-canary' | 'msedge' | 'msedge-beta' | 'msedge-dev' | 'msedge-canary'; -const allDownloadable = ['android', 'chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-tip-of-tree', 'chromium-headless-shell']; +const allDownloadable = ['android', 'chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-tip-of-tree', 'chromium-headless-shell', 'chromium-tip-of-tree-headless-shell']; export interface Executable { type: 'browser' | 'tool' | 'channel'; @@ -514,6 +543,24 @@ export class Registry { _isHermeticInstallation: true, }); + const chromiumTipOfTreeHeadlessShell = descriptors.find(d => d.name === 'chromium-tip-of-tree-headless-shell')!; + const chromiumTipOfTreeHeadlessShellExecutable = findExecutablePath(chromiumTipOfTreeHeadlessShell.dir, 'chromium-headless-shell'); + this._executables.push({ + type: 'channel', + name: 'chromium-tip-of-tree-headless-shell', + browserName: 'chromium', + directory: chromiumTipOfTreeHeadlessShell.dir, + executablePath: () => chromiumTipOfTreeHeadlessShellExecutable, + executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium', chromiumTipOfTreeHeadlessShellExecutable, chromiumTipOfTreeHeadlessShell.installByDefault, sdkLanguage), + installType: chromiumTipOfTreeHeadlessShell.installByDefault ? 'download-by-default' : 'download-on-demand', + _validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, chromiumTipOfTreeHeadlessShell.dir, ['chrome-linux'], [], ['chrome-win']), + downloadURLs: this._downloadURLs(chromiumTipOfTreeHeadlessShell), + browserVersion: chromium.browserVersion, + _install: () => this._downloadExecutable(chromiumTipOfTreeHeadlessShell, chromiumTipOfTreeHeadlessShellExecutable), + _dependencyGroup: 'chromium', + _isHermeticInstallation: true, + }); + const chromiumTipOfTree = descriptors.find(d => d.name === 'chromium-tip-of-tree')!; const chromiumTipOfTreeExecutable = findExecutablePath(chromiumTipOfTree.dir, 'chromium'); this._executables.push({ diff --git a/tests/config/browserTest.ts b/tests/config/browserTest.ts index c729052715..8477359407 100644 --- a/tests/config/browserTest.ts +++ b/tests/config/browserTest.ts @@ -99,7 +99,7 @@ const test = baseTest.extend isWebView2: [false, { scope: 'worker' }], isHeadlessShell: [async ({ browserName, channel, headless }, use) => { - await use(browserName === 'chromium' && (channel === 'chromium-headless-shell' || (!channel && headless))); + await use(browserName === 'chromium' && (channel === 'chromium-headless-shell' || channel === 'chromium-tip-of-tree-headless-shell' || (!channel && headless))); }, { scope: 'worker' }], contextFactory: async ({ _contextFactory }: any, run) => { diff --git a/tests/library/headful.spec.ts b/tests/library/headful.spec.ts index d0c9f99450..b74563850a 100644 --- a/tests/library/headful.spec.ts +++ b/tests/library/headful.spec.ts @@ -19,7 +19,7 @@ import { PNG } from 'playwright-core/lib/utilsBundle'; import { expect, playwrightTest as it } from '../config/browserTest'; it.use({ headless: false }); -it.skip(({ channel }) => channel === 'chromium-headless-shell', 'shell is never headed'); +it.skip(({ channel }) => channel === 'chromium-headless-shell' || channel === 'chromium-tip-of-tree-headless-shell', 'shell is never headed'); it('should have default url when launching browser @smoke', async ({ launchPersistent }) => { const { context } = await launchPersistent(); diff --git a/tests/library/launcher.spec.ts b/tests/library/launcher.spec.ts index bb244c0b60..049e9e1834 100644 --- a/tests/library/launcher.spec.ts +++ b/tests/library/launcher.spec.ts @@ -45,6 +45,7 @@ it('should throw a friendly error if its headed and there is no xserver on linux it.skip(platform !== 'linux'); it.skip(mode.startsWith('service')); it.skip(channel === 'chromium-headless-shell', 'shell is never headed'); + it.skip(channel === 'chromium-tip-of-tree-headless-shell', 'shell is never headed'); const error: Error = await browserType.launch({ headless: false, diff --git a/utils/roll_browser.js b/utils/roll_browser.js index ba2422d77e..93c85db128 100755 --- a/utils/roll_browser.js +++ b/utils/roll_browser.js @@ -100,6 +100,10 @@ Example: const headlessShellBrowser = await browsersJSON.browsers.find(b => b.name === 'chromium-headless-shell'); headlessShellBrowser.revision = revision; headlessShellBrowser.browserVersion = browserVersion; + } else if (browserName === 'chromium-tip-of-tree') { + const tipOfTreeBrowser = await browsersJSON.browsers.find(b => b.name === 'chromium-tip-of-tree-headless-shell'); + tipOfTreeBrowser.revision = revision; + tipOfTreeBrowser.browserVersion = browserVersion; } fs.writeFileSync(path.join(CORE_PATH, 'browsers.json'), JSON.stringify(browsersJSON, null, 2) + '\n'); From 081f455ee9c364c6a08a81738ae3462a512c28f1 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 11 Dec 2024 19:28:39 -0800 Subject: [PATCH 51/85] fix: headless-shell follow-up (#33968) --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index f5ced4b17c..032c648390 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -22,7 +22,7 @@ { "name": "chromium-tip-of-tree-headless-shell", "revision": "1285", - "installByDefault": true, + "installByDefault": false, "browserVersion": "133.0.6887.0" }, { From e3629dc1df67546c10fe0f10d4b8e08ecc3b3d4d Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 11 Dec 2024 23:07:03 -0800 Subject: [PATCH 52/85] fix: validate ffmpeg on context creation (#33903) --- .../src/server/bidi/bidiBrowser.ts | 4 ++ .../src/server/browserContext.ts | 32 +++++++++----- .../src/server/chromium/crBrowser.ts | 4 ++ .../src/server/chromium/crPage.ts | 3 ++ .../src/server/firefox/ffBrowser.ts | 4 ++ packages/playwright-core/src/server/page.ts | 8 +--- .../src/server/webkit/wkBrowser.ts | 4 ++ ...playwright-cli-install-should-work.spec.ts | 44 +++++++++++++++++++ 8 files changed, 84 insertions(+), 19 deletions(-) diff --git a/packages/playwright-core/src/server/bidi/bidiBrowser.ts b/packages/playwright-core/src/server/bidi/bidiBrowser.ts index 9861fc80cf..acefc08fb2 100644 --- a/packages/playwright-core/src/server/bidi/bidiBrowser.ts +++ b/packages/playwright-core/src/server/bidi/bidiBrowser.ts @@ -211,6 +211,10 @@ export class BidiBrowserContext extends BrowserContext { return this._bidiPages().map(bidiPage => bidiPage._initializedPage).filter(Boolean) as Page[]; } + pagesOrErrors() { + return this._bidiPages().map(bidiPage => bidiPage.pageOrError()); + } + async newPageDelegate(): Promise { assertBrowserContextIsNotOwned(this); const { context } = await this._browser._browserSession.send('browsingContext.create', { diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 025bd0f388..6d9227c0a7 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -259,6 +259,7 @@ export abstract class BrowserContext extends SdkObject { // BrowserContext methods. abstract pages(): Page[]; + abstract pagesOrErrors(): Promise[]; abstract newPageDelegate(): Promise; abstract addCookies(cookies: channels.SetNetworkCookie[]): Promise; abstract setGeolocation(geolocation?: types.Geolocation): Promise; @@ -358,29 +359,36 @@ export abstract class BrowserContext extends SdkObject { this._timeoutSettings.setDefaultTimeout(timeout); } - async _loadDefaultContextAsIs(progress: Progress): Promise { - if (!this.pages().length) { + async _loadDefaultContextAsIs(progress: Progress): Promise { + let pageOrError; + if (!this.pagesOrErrors().length) { const waitForEvent = helper.waitForEvent(progress, this, BrowserContext.Events.Page); progress.cleanupWhenAborted(() => waitForEvent.dispose); - const page = (await waitForEvent.promise) as Page; - if (page._pageIsError) - throw page._pageIsError; + // Race against BrowserContext.close + pageOrError = await Promise.race([ + waitForEvent.promise as Promise, + this._closePromise, + ]); + // Consider Page initialization errors + if (pageOrError instanceof Page) + pageOrError = await pageOrError._delegate.pageOrError(); + } else { + pageOrError = await this.pagesOrErrors()[0]; } - const pages = this.pages(); - if (pages[0]._pageIsError) - throw pages[0]._pageIsError; - await pages[0].mainFrame()._waitForLoadState(progress, 'load'); - return pages; + if (pageOrError instanceof Error) + throw pageOrError; + await pageOrError.mainFrame()._waitForLoadState(progress, 'load'); + return pageOrError; } async _loadDefaultContext(progress: Progress) { - const pages = await this._loadDefaultContextAsIs(progress); + const defaultPage = await this._loadDefaultContextAsIs(progress); const browserName = this._browser.options.name; if ((this._options.isMobile && browserName === 'chromium') || (this._options.locale && browserName === 'webkit')) { // Workaround for: // - chromium fails to change isMobile for existing page; // - webkit fails to change locale for existing page. - const oldPage = pages[0]; + const oldPage = defaultPage; await this.newPage(progress.metadata); await oldPage.close(progress.metadata); } diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index a6d77e4ae7..1aba1ed8cb 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -368,6 +368,10 @@ export class CRBrowserContext extends BrowserContext { return this._crPages().map(crPage => crPage._initializedPage).filter(Boolean) as Page[]; } + pagesOrErrors() { + return this._crPages().map(crPage => crPage.pageOrError()); + } + async newPageDelegate(): Promise { assertBrowserContextIsNotOwned(this); diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index bfad678f1c..a7ee34d2c5 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -432,6 +432,9 @@ class FrameSession { this._firstNonInitialNavigationCommittedFulfill = f; this._firstNonInitialNavigationCommittedReject = r; }); + // The Promise is not always awaited (e.g. FrameSession._initialize can throw) + // so we catch errors here to prevent unhandled promise rejection. + this._firstNonInitialNavigationCommittedPromise.catch(() => {}); } _isMainFrame(): boolean { diff --git a/packages/playwright-core/src/server/firefox/ffBrowser.ts b/packages/playwright-core/src/server/firefox/ffBrowser.ts index b26a2850ee..afc0671f70 100644 --- a/packages/playwright-core/src/server/firefox/ffBrowser.ts +++ b/packages/playwright-core/src/server/firefox/ffBrowser.ts @@ -271,6 +271,10 @@ export class FFBrowserContext extends BrowserContext { return this._ffPages().map(ffPage => ffPage._initializedPage).filter(pageOrNull => !!pageOrNull) as Page[]; } + pagesOrErrors() { + return this._ffPages().map(ffPage => ffPage.pageOrError()); + } + async newPageDelegate(): Promise { assertBrowserContextIsNotOwned(this); const { targetId } = await this._browser.session.send('Browser.newPage', { diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index d626b1ed3c..48c0827c08 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -164,7 +164,6 @@ export class Page extends SdkObject { _clientRequestInterceptor: network.RouteHandler | undefined; _serverRequestInterceptor: network.RouteHandler | undefined; _ownedContext: BrowserContext | undefined; - _pageIsError: Error | undefined; _video: Artifact | null = null; _opener: Page | undefined; private _isServerSideOnly = false; @@ -208,7 +207,7 @@ export class Page extends SdkObject { // context/browser closure. Just ignore the page. if (this._browserContext.isClosingOrClosed()) return; - this._setIsError(error); + this._frameManager.createDummyMainFrameIfNeeded(); } this._initialized = true; this.emitOnContext(contextEvent, this); @@ -709,11 +708,6 @@ export class Page extends SdkObject { await this._ownedContext.close(options); } - private _setIsError(error: Error) { - this._pageIsError = error; - this._frameManager.createDummyMainFrameIfNeeded(); - } - isClosed(): boolean { return this._closedState === 'closed'; } diff --git a/packages/playwright-core/src/server/webkit/wkBrowser.ts b/packages/playwright-core/src/server/webkit/wkBrowser.ts index f4f9f732a5..4e5467fd17 100644 --- a/packages/playwright-core/src/server/webkit/wkBrowser.ts +++ b/packages/playwright-core/src/server/webkit/wkBrowser.ts @@ -243,6 +243,10 @@ export class WKBrowserContext extends BrowserContext { return this._wkPages().map(wkPage => wkPage._initializedPage).filter(pageOrNull => !!pageOrNull) as Page[]; } + pagesOrErrors() { + return this._wkPages().map(wkPage => wkPage.pageOrError()); + } + async newPageDelegate(): Promise { assertBrowserContextIsNotOwned(this); const { pageProxyId } = await this._browser._browserSession.send('Playwright.createPage', { browserContextId: this._browserContextId }); diff --git a/tests/installation/playwright-cli-install-should-work.spec.ts b/tests/installation/playwright-cli-install-should-work.spec.ts index 1c0cea6277..d1a8bd3d04 100755 --- a/tests/installation/playwright-cli-install-should-work.spec.ts +++ b/tests/installation/playwright-cli-install-should-work.spec.ts @@ -14,6 +14,7 @@ * limitations under the License. */ import { test, expect } from './npmTest'; +import { chromium } from '@playwright/test'; import path from 'path'; test.use({ isolateBrowsers: true }); @@ -95,3 +96,46 @@ test('install playwright-chromium should work', async ({ exec, installedSoftware await exec('npx playwright install chromium'); await exec('node sanity.js playwright-chromium chromium'); }); + +test('should print error if recording video without ffmpeg', async ({ exec, writeFiles }) => { + await exec('npm i playwright'); + + await writeFiles({ + 'launch.js': ` + const playwright = require('playwright'); + (async () => { + const browser = await playwright.chromium.launch({ executablePath: ${JSON.stringify(chromium.executablePath())} }); + try { + const context = await browser.newContext({ recordVideo: { dir: 'videos' } }); + const page = await context.newPage(); + } finally { + await browser.close(); + } + })().catch(e => { + console.error(e); + process.exit(1); + }); + `, + 'launchPersistentContext.js': ` + const playwright = require('playwright'); + process.on('unhandledRejection', (e) => console.error('unhandledRejection', e)); + (async () => { + const context = await playwright.chromium.launchPersistentContext('', { executablePath: ${JSON.stringify(chromium.executablePath())}, recordVideo: { dir: 'videos' } }); + })().catch(e => { + console.error(e); + process.exit(1); + }); + `, + }); + + await test.step('BrowserType.launch', async () => { + const result = await exec('node', 'launch.js', { expectToExitWithError: true }); + expect(result).toContain(`browserContext.newPage: Executable doesn't exist at`); + }); + + await test.step('BrowserType.launchPersistentContext', async () => { + const result = await exec('node', 'launchPersistentContext.js', { expectToExitWithError: true }); + expect(result).not.toContain('unhandledRejection'); + expect(result).toContain(`browserType.launchPersistentContext: Executable doesn't exist at`); + }); +}); From 0e2b984287e6f83b6cc56b760b940a1df6dbc715 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 12 Dec 2024 08:21:00 -0800 Subject: [PATCH 53/85] chore: prioritize role over label and placeholder (#33970) --- .../src/server/injected/selectorGenerator.ts | 6 ++-- tests/library/inspector/cli-codegen-3.spec.ts | 36 +++++++++---------- tests/library/selector-generator.spec.ts | 26 +++++++------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/playwright-core/src/server/injected/selectorGenerator.ts b/packages/playwright-core/src/server/injected/selectorGenerator.ts index 4b8f704944..04f1598762 100644 --- a/packages/playwright-core/src/server/injected/selectorGenerator.ts +++ b/packages/playwright-core/src/server/injected/selectorGenerator.ts @@ -38,9 +38,9 @@ const kOtherTestIdScore = 2; // other data-test* attributes const kIframeByAttributeScore = 10; const kBeginPenalizedScore = 50; -const kPlaceholderScore = 100; -const kLabelScore = 120; -const kRoleWithNameScore = 140; +const kRoleWithNameScore = 100; +const kPlaceholderScore = 120; +const kLabelScore = 140; const kAltTextScore = 160; const kTextScore = 180; const kTitleScore = 200; diff --git a/tests/library/inspector/cli-codegen-3.spec.ts b/tests/library/inspector/cli-codegen-3.spec.ts index a57e7d5405..22032a8f26 100644 --- a/tests/library/inspector/cli-codegen-3.spec.ts +++ b/tests/library/inspector/cli-codegen-3.spec.ts @@ -472,7 +472,7 @@ await page.GetByTestId("testid").ClickAsync();`); await recorder.setContentAndWait(``); const locator = await recorder.hoverOverElement('input'); - expect(locator).toBe(`getByPlaceholder('Country')`); + expect(locator).toBe(`getByRole('textbox', { name: 'Country' })`); const [sources] = await Promise.all([ recorder.waitForOutput('JavaScript', 'click'), @@ -480,19 +480,19 @@ await page.GetByTestId("testid").ClickAsync();`); ]); expect.soft(sources.get('JavaScript')!.text).toContain(` - await page.getByPlaceholder('Country').click();`); + await page.getByRole('textbox', { name: 'Country' }).click();`); expect.soft(sources.get('Python')!.text).toContain(` - page.get_by_placeholder("Country").click()`); + page.get_by_role("textbox", name="Country").click()`); expect.soft(sources.get('Python Async')!.text).toContain(` - await page.get_by_placeholder("Country").click()`); + await page.get_by_role("textbox", name="Country").click()`); expect.soft(sources.get('Java')!.text).toContain(` - page.getByPlaceholder("Country").click()`); + page.getByRole(AriaRole.TEXTBOX, new Page.GetByRoleOptions().setName("Country")).click()`); expect.soft(sources.get('C#')!.text).toContain(` -await page.GetByPlaceholder("Country").ClickAsync();`); +await page.GetByRole(AriaRole.Textbox, new() { Name = "Country" }).ClickAsync();`); }); test('should generate getByAltText', async ({ openRecorder }) => { @@ -530,7 +530,7 @@ await page.GetByAltText("Country").ClickAsync();`); await recorder.setContentAndWait(``); const locator = await recorder.hoverOverElement('input'); - expect(locator).toBe(`getByLabel('Country')`); + expect(locator).toBe(`getByRole('textbox', { name: 'Country' })`); const [sources] = await Promise.all([ recorder.waitForOutput('JavaScript', 'click'), @@ -538,19 +538,19 @@ await page.GetByAltText("Country").ClickAsync();`); ]); expect.soft(sources.get('JavaScript')!.text).toContain(` - await page.getByLabel('Country').click();`); + await page.getByRole('textbox', { name: 'Country' }).click();`); expect.soft(sources.get('Python')!.text).toContain(` - page.get_by_label("Country").click()`); + page.get_by_role("textbox", name="Country").click()`); expect.soft(sources.get('Python Async')!.text).toContain(` - await page.get_by_label("Country").click()`); + await page.get_by_role("textbox", name="Country").click()`); expect.soft(sources.get('Java')!.text).toContain(` - page.getByLabel("Country").click()`); + page.getByRole(AriaRole.TEXTBOX, new Page.GetByRoleOptions().setName("Country")).click()`); expect.soft(sources.get('C#')!.text).toContain(` -await page.GetByLabel("Country").ClickAsync();`); +await page.GetByRole(AriaRole.Textbox, new() { Name = "Country" }).ClickAsync();`); }); test('should generate getByLabel without regex', async ({ openRecorder }) => { @@ -559,7 +559,7 @@ await page.GetByLabel("Country").ClickAsync();`); await recorder.setContentAndWait(``); const locator = await recorder.hoverOverElement('input'); - expect(locator).toBe(`getByLabel('Coun"try')`); + expect(locator).toBe(`getByRole('textbox', { name: 'Coun"try' })`); const [sources] = await Promise.all([ recorder.waitForOutput('JavaScript', 'click'), @@ -567,19 +567,19 @@ await page.GetByLabel("Country").ClickAsync();`); ]); expect.soft(sources.get('JavaScript')!.text).toContain(` - await page.getByLabel('Coun\"try').click();`); + await page.getByRole('textbox', { name: 'Coun\"try' }).click();`); expect.soft(sources.get('Python')!.text).toContain(` - page.get_by_label("Coun\\"try").click()`); + page.get_by_role("textbox", name="Coun\\"try").click()`); expect.soft(sources.get('Python Async')!.text).toContain(` - await page.get_by_label("Coun\\"try").click()`); + await page.get_by_role("textbox", name="Coun\\"try").click()`); expect.soft(sources.get('Java')!.text).toContain(` - page.getByLabel("Coun\\"try").click()`); + page.getByRole(AriaRole.TEXTBOX, new Page.GetByRoleOptions().setName(\"Coun\\\"try\")).click();`); expect.soft(sources.get('C#')!.text).toContain(` -await page.GetByLabel("Coun\\"try").ClickAsync();`); +await page.GetByRole(AriaRole.Textbox, new() { Name = \"Coun\\\"try\" }).ClickAsync();`); }); test('should consume pointer events', async ({ openRecorder }) => { diff --git a/tests/library/selector-generator.spec.ts b/tests/library/selector-generator.spec.ts index c2e074c49f..1feaf538e7 100644 --- a/tests/library/selector-generator.spec.ts +++ b/tests/library/selector-generator.spec.ts @@ -58,7 +58,7 @@ it.describe('selector generator', () => { it('should not escape spaces inside named attr selectors', async ({ page }) => { await page.setContent(``); - expect(await generate(page, 'input')).toBe('internal:attr=[placeholder=\"Foo b ar\"i]'); + expect(await generate(page, 'input')).toBe('internal:role=textbox[name=\"Foo b ar\"i]'); }); it('should generate text for ', async ({ page }) => { @@ -91,7 +91,7 @@ it.describe('selector generator', () => { it('should try to improve label text by shortening', async ({ page }) => { await page.setContent(``); - expect(await generate(page, 'input')).toBe('internal:label="Longest verbose description"i'); + expect(await generate(page, 'input')).toBe('internal:role=textbox[name=\"Longest verbose description\"i]'); }); it('should not improve guid text', async ({ page }) => { @@ -316,7 +316,7 @@ it.describe('selector generator', () => { }); it('placeholder', async ({ page }) => { await page.setContent(``); - expect(await generate(page, 'input')).toBe('internal:attr=[placeholder=\"foobar\"i]'); + expect(await generate(page, 'input')).toBe('internal:role=textbox[name=\"foobar\"i]'); }); it('name', async ({ page }) => { await page.setContent(` @@ -437,7 +437,7 @@ it.describe('selector generator', () => { it('should accept valid aria-label for candidate consideration', async ({ page }) => { await page.setContent(``); - expect(await generate(page, 'button')).toBe('internal:label="ariaLabel"i'); + expect(await generate(page, 'button')).toBe('internal:role=button[name=\"ariaLabel\"i]'); }); it('should ignore empty role for candidate consideration', async ({ page }) => { @@ -469,15 +469,15 @@ it.describe('selector generator', () => {
text
`); - expect(await generate(page, '#target1')).toBe('internal:label="Target1"i'); - expect(await generate(page, '#target2')).toBe('internal:label="Target2"i'); - expect(await generate(page, '#target3')).toBe('internal:label="Target3"i'); - expect(await generate(page, '#target4')).toBe('internal:label="Target4"i'); - expect(await generate(page, '#target5')).toBe('#target5'); - expect(await generate(page, '#target6')).toBe('internal:text="text"i'); + expect.soft(await generate(page, '#target1')).toBe('internal:role=textbox[name=\"Target1\"i]'); + expect.soft(await generate(page, '#target2')).toBe('internal:role=button[name=\"Target2\"i]'); + expect.soft(await generate(page, '#target3')).toBe('internal:label=\"Target3\"i'); + expect.soft(await generate(page, '#target4')).toBe('internal:label=\"Target4\"i'); + expect.soft(await generate(page, '#target5')).toBe('#target5'); + expect.soft(await generate(page, '#target6')).toBe('internal:text="text"i'); await page.setContent(``); - expect(await generate(page, 'input')).toBe('internal:label="Coun\\\"try"i'); + expect(await generate(page, 'input')).toBe('internal:role=textbox[name=\"Coun\\\"try\"i]'); }); it('should prefer role other input[type]', async ({ page }) => { @@ -514,7 +514,7 @@ it.describe('selector generator', () => { `); - expect(await generate(page, 'input')).toBe('internal:attr=[placeholder=\"Text\"s]'); + expect(await generate(page, 'input')).toBe('internal:role=textbox[name=\"Text\"s]'); }); it('should generate exact role when necessary', async ({ page }) => { @@ -530,7 +530,7 @@ it.describe('selector generator', () => { `); - expect(await generate(page, 'input')).toBe('internal:label=\"Text\"s'); + expect(await generate(page, 'input')).toBe('internal:role=textbox[name=\"Text\"s]'); }); it('should generate relative selector', async ({ page }) => { From 29fd2df124e19541d4a86f3ec277ece821b835dd Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 12 Dec 2024 08:21:53 -0800 Subject: [PATCH 54/85] chore: send aria snapshot to the debug controller (#33969) --- packages/playwright-core/src/protocol/validator.ts | 1 + packages/playwright-core/src/server/debugController.ts | 2 +- .../src/server/dispatchers/debugControllerDispatcher.ts | 4 ++-- packages/protocol/src/channels.ts | 1 + packages/protocol/src/protocol.yml | 1 + tests/library/debug-controller.spec.ts | 2 ++ 6 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 7fe92a7947..f4db833e02 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -385,6 +385,7 @@ scheme.DebugControllerInitializer = tOptional(tObject({})); scheme.DebugControllerInspectRequestedEvent = tObject({ selector: tString, locator: tString, + ariaSnapshot: tString, }); scheme.DebugControllerSetModeRequestedEvent = tObject({ mode: tString, diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index 251dce7e03..8878ecb59c 100644 --- a/packages/playwright-core/src/server/debugController.ts +++ b/packages/playwright-core/src/server/debugController.ts @@ -228,7 +228,7 @@ class InspectingRecorderApp extends EmptyRecorderApp { override async elementPicked(elementInfo: ElementInfo): Promise { const locator: string = asLocator(this._debugController._sdkLanguage, elementInfo.selector); - this._debugController.emit(DebugController.Events.InspectRequested, { selector: elementInfo.selector, locator }); + this._debugController.emit(DebugController.Events.InspectRequested, { selector: elementInfo.selector, locator, ariaSnapshot: elementInfo.ariaSnapshot }); } override async setSources(sources: Source[]): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts index 77d7b503ab..fc722d1bd3 100644 --- a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts @@ -32,8 +32,8 @@ export class DebugControllerDispatcher extends Dispatcher { this._dispatchEvent('stateChanged', params); }), - eventsHelper.addEventListener(this._object, DebugController.Events.InspectRequested, ({ selector, locator }) => { - this._dispatchEvent('inspectRequested', { selector, locator }); + eventsHelper.addEventListener(this._object, DebugController.Events.InspectRequested, ({ selector, locator, ariaSnapshot }) => { + this._dispatchEvent('inspectRequested', { selector, locator, ariaSnapshot }); }), eventsHelper.addEventListener(this._object, DebugController.Events.SourceChanged, ({ text, header, footer, actions }) => { this._dispatchEvent('sourceChanged', ({ text, header, footer, actions })); diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 27c4242fe5..100baf59f9 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -691,6 +691,7 @@ export interface DebugControllerChannel extends DebugControllerEventTarget, Chan export type DebugControllerInspectRequestedEvent = { selector: string, locator: string, + ariaSnapshot: string, }; export type DebugControllerSetModeRequestedEvent = { mode: string, diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index d9597c2295..9997989f77 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -807,6 +807,7 @@ DebugController: parameters: selector: string locator: string + ariaSnapshot: string setModeRequested: parameters: diff --git a/tests/library/debug-controller.spec.ts b/tests/library/debug-controller.spec.ts index 6050d4e645..c3fb643665 100644 --- a/tests/library/debug-controller.spec.ts +++ b/tests/library/debug-controller.spec.ts @@ -84,9 +84,11 @@ test('should pick element', async ({ backend, connectedBrowser }) => { expect(events).toEqual([ { + ariaSnapshot: '- button "Submit"', selector: 'internal:role=button[name=\"Submit\"i]', locator: 'getByRole(\'button\', { name: \'Submit\' })', }, { + ariaSnapshot: '- button "Submit"', selector: 'internal:role=button[name=\"Submit\"i]', locator: 'getByRole(\'button\', { name: \'Submit\' })', }, From fc9f5a6f28bd087a42468ba18178c987f696948a Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:04:17 -0800 Subject: [PATCH 55/85] feat(chromium): roll to r1152 (#33977) --- packages/playwright-core/browsers.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 032c648390..6d73308769 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,15 +3,15 @@ "browsers": [ { "name": "chromium", - "revision": "1151", + "revision": "1152", "installByDefault": true, - "browserVersion": "132.0.6834.32" + "browserVersion": "132.0.6834.46" }, { "name": "chromium-headless-shell", - "revision": "1151", + "revision": "1152", "installByDefault": true, - "browserVersion": "132.0.6834.32" + "browserVersion": "132.0.6834.46" }, { "name": "chromium-tip-of-tree", From aca00a4ab005c9991fb33891c42d24c635614055 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 12 Dec 2024 11:06:14 -0800 Subject: [PATCH 56/85] chore: update README/device descriptors after roll (#33982) --- README.md | 4 +- .../src/server/deviceDescriptorsSource.json | 96 +++++++++---------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 6203130f2e..913aac3269 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🎭 Playwright -[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-132.0.6834.32-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-132.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-132.0.6834.46-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-132.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) @@ -8,7 +8,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 132.0.6834.32 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 132.0.6834.46 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 18.2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 132.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index 179eb52f7b..49d9edde41 100644 --- a/packages/playwright-core/src/server/deviceDescriptorsSource.json +++ b/packages/playwright-core/src/server/deviceDescriptorsSource.json @@ -110,7 +110,7 @@ "defaultBrowserType": "webkit" }, "Galaxy S5": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -121,7 +121,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -132,7 +132,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 360, "height": 740 @@ -143,7 +143,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S8 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 740, "height": 360 @@ -154,7 +154,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 320, "height": 658 @@ -165,7 +165,7 @@ "defaultBrowserType": "chromium" }, "Galaxy S9+ landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 658, "height": 320 @@ -176,7 +176,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", "viewport": { "width": 712, "height": 1138 @@ -187,7 +187,7 @@ "defaultBrowserType": "chromium" }, "Galaxy Tab S4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", "viewport": { "width": 1138, "height": 712 @@ -1098,7 +1098,7 @@ "defaultBrowserType": "webkit" }, "LG Optimus L70": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1109,7 +1109,7 @@ "defaultBrowserType": "chromium" }, "LG Optimus L70 landscape": { - "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1120,7 +1120,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1131,7 +1131,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 550 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1142,7 +1142,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 360, "height": 640 @@ -1153,7 +1153,7 @@ "defaultBrowserType": "chromium" }, "Microsoft Lumia 950 landscape": { - "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36 Edge/14.14263", + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36 Edge/14.14263", "viewport": { "width": 640, "height": 360 @@ -1164,7 +1164,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", "viewport": { "width": 800, "height": 1280 @@ -1175,7 +1175,7 @@ "defaultBrowserType": "chromium" }, "Nexus 10 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", "viewport": { "width": 1280, "height": 800 @@ -1186,7 +1186,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 384, "height": 640 @@ -1197,7 +1197,7 @@ "defaultBrowserType": "chromium" }, "Nexus 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 640, "height": 384 @@ -1208,7 +1208,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1219,7 +1219,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1230,7 +1230,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1241,7 +1241,7 @@ "defaultBrowserType": "chromium" }, "Nexus 5X landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1252,7 +1252,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1263,7 +1263,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1274,7 +1274,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 412, "height": 732 @@ -1285,7 +1285,7 @@ "defaultBrowserType": "chromium" }, "Nexus 6P landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 732, "height": 412 @@ -1296,7 +1296,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", "viewport": { "width": 600, "height": 960 @@ -1307,7 +1307,7 @@ "defaultBrowserType": "chromium" }, "Nexus 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", "viewport": { "width": 960, "height": 600 @@ -1362,7 +1362,7 @@ "defaultBrowserType": "webkit" }, "Pixel 2": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 411, "height": 731 @@ -1373,7 +1373,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 731, "height": 411 @@ -1384,7 +1384,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 411, "height": 823 @@ -1395,7 +1395,7 @@ "defaultBrowserType": "chromium" }, "Pixel 2 XL landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 823, "height": 411 @@ -1406,7 +1406,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 393, "height": 786 @@ -1417,7 +1417,7 @@ "defaultBrowserType": "chromium" }, "Pixel 3 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 786, "height": 393 @@ -1428,7 +1428,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 353, "height": 745 @@ -1439,7 +1439,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 745, "height": 353 @@ -1450,7 +1450,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G)": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "screen": { "width": 412, "height": 892 @@ -1465,7 +1465,7 @@ "defaultBrowserType": "chromium" }, "Pixel 4a (5G) landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "screen": { "height": 892, "width": 412 @@ -1480,7 +1480,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "screen": { "width": 393, "height": 851 @@ -1495,7 +1495,7 @@ "defaultBrowserType": "chromium" }, "Pixel 5 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "screen": { "width": 851, "height": 393 @@ -1510,7 +1510,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "screen": { "width": 412, "height": 915 @@ -1525,7 +1525,7 @@ "defaultBrowserType": "chromium" }, "Pixel 7 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "screen": { "width": 915, "height": 412 @@ -1540,7 +1540,7 @@ "defaultBrowserType": "chromium" }, "Moto G4": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 360, "height": 640 @@ -1551,7 +1551,7 @@ "defaultBrowserType": "chromium" }, "Moto G4 landscape": { - "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Mobile Safari/537.36", + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Mobile Safari/537.36", "viewport": { "width": 640, "height": 360 @@ -1562,7 +1562,7 @@ "defaultBrowserType": "chromium" }, "Desktop Chrome HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", "screen": { "width": 1792, "height": 1120 @@ -1577,7 +1577,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36 Edg/132.0.6834.32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36 Edg/132.0.6834.46", "screen": { "width": 1792, "height": 1120 @@ -1622,7 +1622,7 @@ "defaultBrowserType": "webkit" }, "Desktop Chrome": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36", "screen": { "width": 1920, "height": 1080 @@ -1637,7 +1637,7 @@ "defaultBrowserType": "chromium" }, "Desktop Edge": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.32 Safari/537.36 Edg/132.0.6834.32", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.46 Safari/537.36 Edg/132.0.6834.46", "screen": { "width": 1920, "height": 1080 From a4add6ebaf404de3898493acd931ed41b751be26 Mon Sep 17 00:00:00 2001 From: Jozef Izso Date: Thu, 12 Dec 2024 20:12:58 +0100 Subject: [PATCH 57/85] Fix typo in `AndroidServerLauncherImpl` class when on device was found (#33973) --- packages/playwright-core/src/androidServerImpl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/src/androidServerImpl.ts b/packages/playwright-core/src/androidServerImpl.ts index a0d7bb5496..f0108e67c7 100644 --- a/packages/playwright-core/src/androidServerImpl.ts +++ b/packages/playwright-core/src/androidServerImpl.ts @@ -38,7 +38,7 @@ export class AndroidServerLauncherImpl { if (options.deviceSerialNumber) { devices = devices.filter(d => d.serial === options.deviceSerialNumber); if (devices.length === 0) - throw new Error(`No device with serial number '${options.deviceSerialNumber}' not found`); + throw new Error(`No device with serial number '${options.deviceSerialNumber}' was found`); } if (devices.length > 1) From 38758c0596c013335ec3e2bfadd7373cf121ee72 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 12 Dec 2024 12:23:03 -0800 Subject: [PATCH 58/85] chore: tidy up headless-shell hacks (#33967) --- packages/playwright-core/browsers.json | 12 ------------ .../playwright-core/src/server/registry/index.ts | 9 ++++++++- utils/roll_browser.js | 12 ------------ 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 6d73308769..397e55cb20 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -7,24 +7,12 @@ "installByDefault": true, "browserVersion": "132.0.6834.46" }, - { - "name": "chromium-headless-shell", - "revision": "1152", - "installByDefault": true, - "browserVersion": "132.0.6834.46" - }, { "name": "chromium-tip-of-tree", "revision": "1285", "installByDefault": false, "browserVersion": "133.0.6887.0" }, - { - "name": "chromium-tip-of-tree-headless-shell", - "revision": "1285", - "installByDefault": false, - "browserVersion": "133.0.6887.0" - }, { "name": "firefox", "revision": "1466", diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index 6326824534..4bb27bcaea 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -415,7 +415,14 @@ type BrowsersJSONDescriptor = { }; function readDescriptors(browsersJSON: BrowsersJSON): BrowsersJSONDescriptor[] { - return (browsersJSON['browsers']).map(obj => { + const headlessShells: BrowsersJSON['browsers'] = []; + for (const browserName of ['chromium', 'chromium-tip-of-tree']) { + headlessShells.push({ + ...browsersJSON.browsers.find(browser => browser.name === browserName)!, + name: `${browserName}-headless-shell`, + }); + } + return [...browsersJSON.browsers, ...headlessShells].map(obj => { const name = obj.name; const revisionOverride = (obj.revisionOverrides || {})[hostPlatform]; const revision = revisionOverride || obj.revision; diff --git a/utils/roll_browser.js b/utils/roll_browser.js index 93c85db128..324147d1aa 100755 --- a/utils/roll_browser.js +++ b/utils/roll_browser.js @@ -94,18 +94,6 @@ Example: console.log('\nUpdating browser version in browsers.json...'); for (const descriptor of descriptors) descriptor.browserVersion = browserVersion; - - // 4.1 chromium-headless-shell is equal to chromium version. - if (browserName === 'chromium') { - const headlessShellBrowser = await browsersJSON.browsers.find(b => b.name === 'chromium-headless-shell'); - headlessShellBrowser.revision = revision; - headlessShellBrowser.browserVersion = browserVersion; - } else if (browserName === 'chromium-tip-of-tree') { - const tipOfTreeBrowser = await browsersJSON.browsers.find(b => b.name === 'chromium-tip-of-tree-headless-shell'); - tipOfTreeBrowser.revision = revision; - tipOfTreeBrowser.browserVersion = browserVersion; - } - fs.writeFileSync(path.join(CORE_PATH, 'browsers.json'), JSON.stringify(browsersJSON, null, 2) + '\n'); } From 16a1552e7490139f295086ab52cc0d7b3901eaac Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 12 Dec 2024 15:25:13 -0800 Subject: [PATCH 59/85] chore: remove 'npx playwright debug' (#33987) --- packages/playwright-core/src/cli/program.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/playwright-core/src/cli/program.ts b/packages/playwright-core/src/cli/program.ts index 7d0659d26f..50a69dc9ea 100644 --- a/packages/playwright-core/src/cli/program.ts +++ b/packages/playwright-core/src/cli/program.ts @@ -31,7 +31,6 @@ import type { Browser } from '../client/browser'; import type { Page } from '../client/page'; import type { BrowserType } from '../client/browserType'; import type { BrowserContextOptions, LaunchOptions } from '../client/types'; -import { spawn } from 'child_process'; import { wrapInASCIIBox, isLikelyNpxGlobal, assert, gracefullyProcessExitDoNotHang, getPackageManagerExecCommand } from '../utils'; import type { Executable } from '../server'; import { registry, writeDockerVersion } from '../server'; @@ -77,21 +76,6 @@ Examples: $ codegen --target=python $ codegen -b webkit https://example.com`); -program - .command('debug [args...]', { hidden: true }) - .description('run command in debug mode: disable timeout, open inspector') - .allowUnknownOption(true) - .action(function(app, options) { - spawn(app, options, { - env: { ...process.env, PWDEBUG: '1' }, - stdio: 'inherit' - }); - }).addHelpText('afterAll', ` -Examples: - - $ debug node test.js - $ debug npm run test`); - function suggestedBrowsersToInstall() { return registry.executables().filter(e => e.installType !== 'none' && e.type !== 'tool').map(e => e.name).join(', '); } From 21c456b2c10d036f33b38ecc216be6af62da543a Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 12 Dec 2024 15:37:53 -0800 Subject: [PATCH 60/85] chore: remove vite default scripts in package.json's (#33986) --- packages/html-reporter/package.json | 7 +------ packages/recorder/package.json | 5 ----- packages/trace-viewer/package.json | 8 +------- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/packages/html-reporter/package.json b/packages/html-reporter/package.json index c1beb75b89..0b5ad5fb24 100644 --- a/packages/html-reporter/package.json +++ b/packages/html-reporter/package.json @@ -2,10 +2,5 @@ "name": "html-reporter", "private": true, "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build && tsc", - "preview": "vite preview" - } + "type": "module" } diff --git a/packages/recorder/package.json b/packages/recorder/package.json index 0bad170fee..97e52eb9a5 100644 --- a/packages/recorder/package.json +++ b/packages/recorder/package.json @@ -3,11 +3,6 @@ "private": true, "version": "0.0.0", "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build && tsc", - "preview": "vite preview" - }, "dependencies": { "yaml": "^2.6.0" } diff --git a/packages/trace-viewer/package.json b/packages/trace-viewer/package.json index e1b745bf83..66cf4e1e58 100644 --- a/packages/trace-viewer/package.json +++ b/packages/trace-viewer/package.json @@ -2,11 +2,5 @@ "name": "trace-viewer", "private": true, "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build && tsc", - "build-sw": "vite --config vite.sw.config.ts build && tsc", - "preview": "vite preview" - } + "type": "module" } From e4413f208927d86e47184e6c9007badc8234ee5a Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 12 Dec 2024 16:23:13 -0800 Subject: [PATCH 61/85] docs: add docs for 'run-server' (#33989) --- docs/src/docker.md | 82 +++++++++++++++++++++ packages/playwright-core/src/cli/program.ts | 2 +- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/docs/src/docker.md b/docs/src/docker.md index b2a3e906b9..abb647e35b 100644 --- a/docs/src/docker.md +++ b/docs/src/docker.md @@ -103,6 +103,88 @@ Using `--ipc=host` is recommended when using Chrome ([Docker docs](https://docs. See our [Continuous Integration guides](./ci.md) for sample configs. +### Remote Connection + +You can run Playwright Server in Docker while keeping your tests running on the host system or another machine. This is useful for running tests on unsupported Linux distributions or remote execution scenarios. + +#### Running the Playwright Server + +Start the Playwright Server in Docker: + +```bash +docker run -p 3000:3000 --rm --init -it --workdir /home/pwuser --user pwuser mcr.microsoft.com/playwright:v%%VERSION%%-noble /bin/sh -c "npx -y playwright@%%VERSION%% run-server --port 3000 --host 0.0.0.0" +``` + +#### Connecting to the Server +* langs: js + +There are two ways to connect to the remote Playwright server: + +1. Using environment variable with `@playwright/test`: + +```bash +PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:3000/ npx playwright test +``` + +2. Using the [`method: BrowserType.connect`] API for other applications: + +```js +const browser = await playwright['chromium'].connect('ws://127.0.0.1:3000/'); +``` + +#### Connecting to the Server +* langs: python, csharp, java + +```python sync +from playwright.sync_api import sync_playwright + +with sync_playwright() as p: + browser = p.chromium.connect("ws://127.0.0.1:3000/") +``` + +```python async +from playwright.async_api import async_playwright + +async with async_playwright() as p: + browser = await p.chromium.connect("ws://127.0.0.1:3000/") +``` + +```csharp +using Microsoft.Playwright; + +using var playwright = await Playwright.CreateAsync(); +await using var browser = await playwright.Chromium.ConnectAsync("ws://127.0.0.1:3000/"); +``` + +```java +package org.example; + +import com.microsoft.playwright.*; +import java.nio.file.Paths; + +public class App { + public static void main(String[] args) { + try (Playwright playwright = Playwright.create()) { + Browser browser = playwright.chromium().connect("ws://127.0.0.1:3000/"); + } + } +} +``` + +#### Network Configuration + +If you need to access local servers from within the Docker container: + +```bash +docker run --add-host=hostmachine:host-gateway -p 3000:3000 --rm --init -it --workdir /home/pwuser --user pwuser mcr.microsoft.com/playwright:v%%VERSION%%-noble /bin/sh -c "npx -y playwright@%%VERSION%% run-server --port 3000 --host 0.0.0.0" +``` + +This makes `hostmachine` point to the host's localhost. Your tests should use `hostmachine` instead of `localhost` when accessing local servers. + +:::note +When running tests remotely, ensure the Playwright version in your tests matches the version running in the Docker container. +::: + ## Image tags See [all available image tags]. diff --git a/packages/playwright-core/src/cli/program.ts b/packages/playwright-core/src/cli/program.ts index 50a69dc9ea..7ce1c4f928 100644 --- a/packages/playwright-core/src/cli/program.ts +++ b/packages/playwright-core/src/cli/program.ts @@ -275,7 +275,7 @@ program }); program - .command('run-server', { hidden: true }) + .command('run-server') .option('--port ', 'Server port') .option('--host ', 'Server host') .option('--path ', 'Endpoint Path', '/') From 76d46d478f65578bcdbc23d7d343ceab31c31a96 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 12 Dec 2024 17:49:31 -0800 Subject: [PATCH 62/85] fix: text-is() should ignore comments (#33980) --- .../src/server/injected/selectorUtils.ts | 2 ++ tests/page/selectors-text.spec.ts | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/packages/playwright-core/src/server/injected/selectorUtils.ts b/packages/playwright-core/src/server/injected/selectorUtils.ts index 746e12af9d..da0f869fa0 100644 --- a/packages/playwright-core/src/server/injected/selectorUtils.ts +++ b/packages/playwright-core/src/server/injected/selectorUtils.ts @@ -73,6 +73,8 @@ export function elementText(cache: Map, root: if (child.nodeType === Node.TEXT_NODE) { value.full += child.nodeValue || ''; currentImmediate += child.nodeValue || ''; + } else if (child.nodeType === Node.COMMENT_NODE) { + continue; } else { if (currentImmediate) value.immediate.push(currentImmediate); diff --git a/tests/page/selectors-text.spec.ts b/tests/page/selectors-text.spec.ts index af9a2d2730..3793cdac5c 100644 --- a/tests/page/selectors-text.spec.ts +++ b/tests/page/selectors-text.spec.ts @@ -183,6 +183,17 @@ it('should work across nodes', async ({ page }) => { expect(await page.$$eval(`text=/world/`, els => els.length)).toBe(1); }); +it('text-is() should ignore comments', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/33976' } +}, async ({ page }) => { + await page.setContent(`
hello + + world
`); + expect(await page.$eval(`:text-is("hello world")`, e => e.id)).toBe('me'); + expect(await page.locator('div', { hasText: 'hello world' }).getAttribute('id')).toBe('me'); + expect(await page.getByText('hello world', { exact: true }).getAttribute('id')).toBe('me'); +}); + it('should work with text nodes in quoted mode', async ({ page }) => { await page.setContent(`
Hellowo rld Hi again
`); expect(await page.$eval(`text="Hello"`, e => e.id)).toBe('target1'); From 0034c6b9844f74f98a2a3aa010071ccc065fbe25 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 12 Dec 2024 17:49:48 -0800 Subject: [PATCH 63/85] fix: parse locator with empty options (#33990) --- .../src/utils/isomorphic/locatorParser.ts | 6 ++++-- tests/library/locator-generator.spec.ts | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/utils/isomorphic/locatorParser.ts b/packages/playwright-core/src/utils/isomorphic/locatorParser.ts index e0c10b53fd..9bae0a62bd 100644 --- a/packages/playwright-core/src/utils/isomorphic/locatorParser.ts +++ b/packages/playwright-core/src/utils/isomorphic/locatorParser.ts @@ -91,7 +91,8 @@ function parseLocator(locator: string, testIdAttributeName: string): { selector: .replace(/newregex\(([^)]+)\)/g, 'r$1') .replace(/string=/g, '=') .replace(/regex=/g, '=') - .replace(/,,/g, ','); + .replace(/,,/g, ',') + .replace(/,\)/g, ')'); const preferredQuote = params.map(p => p.quote).filter(quote => '\'"`'.includes(quote))[0] as Quote | undefined; return { selector: transform(template, params, testIdAttributeName), preferredQuote }; @@ -174,6 +175,7 @@ function transform(template: string, params: TemplateParams, testIdAttributeName .replace(/filter\(,?hasnot2=([^)]+)\)/g, 'internal:has-not=$1') .replace(/,exact=false/g, '') .replace(/,exact=true/g, 's') + .replace(/,includehidden=/g, ',include-hidden=') .replace(/\,/g, ']['); const parts = template.split('.'); @@ -233,6 +235,6 @@ export function locatorOrSelectorAsSelector(language: Language, locator: string, function digestForComparison(language: Language, locator: string) { locator = locator.replace(/\s/g, ''); if (language === 'javascript') - locator = locator.replace(/\\?["`]/g, '\''); + locator = locator.replace(/\\?["`]/g, '\'').replace(/,{}/g, ''); return locator; } diff --git a/tests/library/locator-generator.spec.ts b/tests/library/locator-generator.spec.ts index 4df72977f4..cceff133d4 100644 --- a/tests/library/locator-generator.spec.ts +++ b/tests/library/locator-generator.spec.ts @@ -196,6 +196,12 @@ it('reverse engineer getByRole', async ({ page }) => { java: `getByRole(AriaRole.BUTTON)`, csharp: `GetByRole(AriaRole.Button)`, }); + expect.soft(generate(page.getByRole('heading', {}))).toEqual({ + javascript: "getByRole('heading')", + python: 'get_by_role("heading")', + java: 'getByRole(AriaRole.HEADING)', + csharp: 'GetByRole(AriaRole.Heading)' + }); expect.soft(generate(page.getByRole('button', { name: 'Hello' }))).toEqual({ javascript: `getByRole('button', { name: 'Hello' })`, python: `get_by_role("button", name="Hello")`, @@ -559,6 +565,12 @@ it('parseLocator css', async () => { expect.soft(parseLocator('csharp', `Locator("css=.foo")`, '')).toBe(`css=.foo`); }); + +it('parseLocator options', async () => { + expect.soft(parseLocator('javascript', `getByRole('heading', {})`, '')).toBe(`internal:role=heading`); + expect.soft(parseLocator('javascript', `getByRole('checkbox', { checked:false, includeHidden: true })`, '')).toBe(`internal:role=checkbox[checked=false][include-hidden=true]`); +}); + it('parse locators strictly', () => { const selector = 'div >> internal:has-text=\"Goodbye world\"i >> span'; From dd41930e724139179783abf0ac1326a6036a242b Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:22:27 -0800 Subject: [PATCH 64/85] feat(webkit): roll to r2119 (#33992) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 397e55cb20..205a6aa8dc 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -27,7 +27,7 @@ }, { "name": "webkit", - "revision": "2118", + "revision": "2119", "installByDefault": true, "revisionOverrides": { "debian11-x64": "2105", From b0cec5b3518b8fa5d107068ea39612bcc1341b53 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:22:48 -0800 Subject: [PATCH 65/85] feat(chromium-tip-of-tree): roll to r1286 (#33991) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 205a6aa8dc..a4d1786903 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -9,9 +9,9 @@ }, { "name": "chromium-tip-of-tree", - "revision": "1285", + "revision": "1286", "installByDefault": false, - "browserVersion": "133.0.6887.0" + "browserVersion": "133.0.6891.0" }, { "name": "firefox", From dd36de7809af2ff19e03d4b1c69fc82a283a23ee Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 13 Dec 2024 12:01:20 +0100 Subject: [PATCH 66/85] fix(html): encode all stdio attachments (#33950) --- packages/playwright/src/reporters/html.ts | 11 ++--------- tests/playwright-test/reporter-html.spec.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index 406be15e83..1802eba4fe 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -602,17 +602,10 @@ type JsonAttachment = { }; function stdioAttachment(chunk: Buffer | string, type: 'stdout' | 'stderr'): JsonAttachment { - if (typeof chunk === 'string') { - return { - name: type, - contentType: 'text/plain', - body: chunk - }; - } return { name: type, - contentType: 'application/octet-stream', - body: chunk + contentType: 'text/plain', + body: typeof chunk === 'string' ? chunk : chunk.toString('utf-8') }; } diff --git a/tests/playwright-test/reporter-html.spec.ts b/tests/playwright-test/reporter-html.spec.ts index 4b294126b9..8ec8c6c33c 100644 --- a/tests/playwright-test/reporter-html.spec.ts +++ b/tests/playwright-test/reporter-html.spec.ts @@ -2562,6 +2562,24 @@ for (const useIntermediateMergeReport of [true, false] as const) { - button "tests/b/test.spec.ts" `); }); + + test('execSync doesnt produce a second stdout attachment', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/33886' } }, async ({ runInlineTest, showReport, page }) => { + await runInlineTest({ + 'a.test.js': ` + const { test, expect } = require('@playwright/test'); + const { execSync } = require('node:child_process'); + test('my test', async ({}) => { + console.log('foo'); + execSync('echo bar', { stdio: 'inherit' }); + console.log('baz'); + }); + `, + }, { reporter: 'dot,html' }); + + await showReport(); + await page.getByText('my test').click(); + await expect(page.locator('.tree-item', { hasText: 'stdout' })).toHaveCount(1); + }); }); } From 65688d623ef00cf9688abf98e928d2cb39328537 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 13 Dec 2024 04:52:04 -0800 Subject: [PATCH 67/85] chore: update TypeScript to v5.7 (#33994) --- package-lock.json | 251 ++++++++++-------- package.json | 4 +- packages/html-reporter/src/index.tsx | 2 +- .../playwright-core/src/client/electron.ts | 2 +- .../src/server/chromium/crAccessibility.ts | 2 +- .../src/server/webkit/wkPage.ts | 2 +- packages/playwright/src/common/testLoader.ts | 2 +- packages/playwright/src/reporters/html.ts | 2 +- packages/playwright/src/runner/testServer.ts | 2 +- packages/trace-viewer/src/sw/lruCache.ts | 2 +- .../src/ui/networkResourceDetails.tsx | 4 +- packages/web/src/components/treeView.tsx | 2 +- tests/components/ct-vue-cli/package.json | 6 +- tests/components/ct-vue-cli/tsconfig.app.json | 2 +- .../ct-vue-cli/tsconfig.config.json | 2 +- .../components/ct-vue-vite/tsconfig.node.json | 9 - 16 files changed, 151 insertions(+), 145 deletions(-) delete mode 100644 tests/components/ct-vue-vite/tsconfig.node.json diff --git a/package-lock.json b/package-lock.json index 9341f8d676..0c4d97edad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@types/codemirror": "^5.60.7", "@types/formidable": "^2.0.4", "@types/immutable": "^3.8.7", - "@types/node": "^18.19.39", + "@types/node": "^18.19.68", "@types/react": "^18.0.12", "@types/react-dom": "^18.0.5", "@types/ws": "^8.5.3", @@ -60,7 +60,7 @@ "react": "^18.1.0", "react-dom": "^18.1.0", "ssim.js": "^3.5.0", - "typescript": "^5.5.3", + "typescript": "^5.7.2", "vite": "^5.4.6", "ws": "^8.17.1", "xml2js": "^0.5.0", @@ -397,17 +397,19 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -447,9 +449,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.3" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -810,13 +816,13 @@ } }, "node_modules/@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1409,9 +1415,10 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.21", @@ -1851,10 +1858,11 @@ } }, "node_modules/@types/node": { - "version": "18.19.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz", - "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==", + "version": "18.19.68", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.68.tgz", + "integrity": "sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw==", "devOptional": true, + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } @@ -2184,126 +2192,126 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz", - "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/shared": "3.4.15", + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" - } - }, - "node_modules/@vue/compiler-core/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "peer": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-core/node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT", "peer": true }, "node_modules/@vue/compiler-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz", - "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "license": "MIT", "peer": true, "dependencies": { - "@vue/compiler-core": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz", - "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "license": "MIT", "peer": true, "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/compiler-core": "3.4.15", - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15", + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", - "magic-string": "^0.30.5", - "postcss": "^8.4.33", - "source-map-js": "^1.0.2" + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT", "peer": true }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz", - "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "license": "MIT", "peer": true, "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/reactivity": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz", - "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "license": "MIT", "peer": true, "dependencies": { - "@vue/shared": "3.4.15" + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz", - "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "license": "MIT", "peer": true, "dependencies": { - "@vue/reactivity": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz", - "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "license": "MIT", "peer": true, "dependencies": { - "@vue/runtime-core": "3.4.15", - "@vue/shared": "3.4.15", + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz", - "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "license": "MIT", "peer": true, "dependencies": { - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { - "vue": "3.4.15" + "vue": "3.5.13" } }, "node_modules/@vue/shared": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz", - "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "license": "MIT", "peer": true }, "node_modules/@zip.js/zip.js": { @@ -3326,6 +3334,19 @@ "once": "^1.4.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -5236,14 +5257,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.15", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.15.tgz", + "integrity": "sha512-zXeaYRgZ6ldS1RJJUrMrYgNJ4fdwnyI6tVqoiIhyCyv5IVTK9BU8Ic2l253GGETQHxI4HNUwhJ3fjDhKqEoaAw==", + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -5773,9 +5792,10 @@ } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -5828,9 +5848,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -5845,9 +5865,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -6721,14 +6742,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6889,10 +6902,11 @@ } }, "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "devOptional": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7451,16 +7465,17 @@ } }, "node_modules/vue": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz", - "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "license": "MIT", "peer": true, "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-sfc": "3.4.15", - "@vue/runtime-dom": "3.4.15", - "@vue/server-renderer": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" diff --git a/package.json b/package.json index f36e9039e3..5eafd4d805 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@types/codemirror": "^5.60.7", "@types/formidable": "^2.0.4", "@types/immutable": "^3.8.7", - "@types/node": "^18.19.39", + "@types/node": "^18.19.68", "@types/react": "^18.0.12", "@types/react-dom": "^18.0.5", "@types/ws": "^8.5.3", @@ -99,7 +99,7 @@ "react": "^18.1.0", "react-dom": "^18.1.0", "ssim.js": "^3.5.0", - "typescript": "^5.5.3", + "typescript": "^5.7.2", "vite": "^5.4.6", "ws": "^8.17.1", "xml2js": "^0.5.0", diff --git a/packages/html-reporter/src/index.tsx b/packages/html-reporter/src/index.tsx index 16c1ae1a88..4ae3b02591 100644 --- a/packages/html-reporter/src/index.tsx +++ b/packages/html-reporter/src/index.tsx @@ -15,7 +15,7 @@ */ import type { HTMLReport } from './types'; -import type zip from '@zip.js/zip.js'; +import type * as zip from '@zip.js/zip.js'; // @ts-ignore import * as zipImport from '@zip.js/zip.js/lib/zip-no-worker-inflate.js'; import * as React from 'react'; diff --git a/packages/playwright-core/src/client/electron.ts b/packages/playwright-core/src/client/electron.ts index fcabfa494f..fe5b6e189e 100644 --- a/packages/playwright-core/src/client/electron.ts +++ b/packages/playwright-core/src/client/electron.ts @@ -103,7 +103,7 @@ export class ElectronApplication extends ChannelOwner { if (this._windows.size) - return this._windows.values().next().value; + return this._windows.values().next().value!; return await this.waitForEvent('window', options); } diff --git a/packages/playwright-core/src/server/chromium/crAccessibility.ts b/packages/playwright-core/src/server/chromium/crAccessibility.ts index 30d02ee7bf..4114663a0e 100644 --- a/packages/playwright-core/src/server/chromium/crAccessibility.ts +++ b/packages/playwright-core/src/server/chromium/crAccessibility.ts @@ -299,6 +299,6 @@ class CRAXNode implements accessibility.AXNode { for (const childId of node._payload.childIds || []) node._children.push(nodeById.get(childId)!); } - return nodeById.values().next().value; + return nodeById.values().next().value!; } } diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index a6e2145ce4..a1e69e1267 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -849,7 +849,7 @@ export class WKPage implements PageDelegate { this.validateScreenshotDimension(rect.height, omitDeviceScaleFactor); const result = await this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport', omitDeviceScaleFactor }); const prefix = 'data:image/png;base64,'; - let buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64'); + let buffer: Buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64'); if (format === 'jpeg') buffer = jpegjs.encode(PNG.sync.read(buffer), quality).data; return buffer; diff --git a/packages/playwright/src/common/testLoader.ts b/packages/playwright/src/common/testLoader.ts index a85d93545d..6a8940ee19 100644 --- a/packages/playwright/src/common/testLoader.ts +++ b/packages/playwright/src/common/testLoader.ts @@ -68,7 +68,7 @@ export async function loadTestFile(file: string, rootDir: string, testErrors?: T suite.allTests().map(t => files.add(t.location.file)); if (files.size === 1) { // All tests point to one file. - const mappedFile = files.values().next().value; + const mappedFile = files.values().next().value!; if (suite.location.file !== mappedFile) { // The file is different, check for a likely source map case. if (path.extname(mappedFile) !== path.extname(suite.location.file)) diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index 1802eba4fe..302d532641 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -309,7 +309,7 @@ class HtmlBuilder { let singleTestId: string | undefined; if (htmlReport.stats.total === 1) { - const testFile: TestFile = data.values().next().value.testFile; + const testFile: TestFile = data.values().next().value!.testFile; singleTestId = testFile.tests[0].testId; } diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index 981a1580f0..30f724c03e 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -479,7 +479,7 @@ type StdioPayload = { }; function chunkToPayload(type: 'stdout' | 'stderr', chunk: Buffer | string): StdioPayload { - if (chunk instanceof Buffer) + if (chunk instanceof Uint8Array) return { type, buffer: chunk.toString('base64') }; return { type, text: chunk }; } diff --git a/packages/trace-viewer/src/sw/lruCache.ts b/packages/trace-viewer/src/sw/lruCache.ts index 573a15ea49..2d5eae8542 100644 --- a/packages/trace-viewer/src/sw/lruCache.ts +++ b/packages/trace-viewer/src/sw/lruCache.ts @@ -37,7 +37,7 @@ export class LRUCache { const result = compute(); while (this._map.size && this._size + result.size > this._maxSize) { - const [firstKey, firstValue] = this._map.entries().next().value; + const [firstKey, firstValue] = this._map.entries().next().value!; this._size -= firstValue.size; this._map.delete(firstKey); } diff --git a/packages/trace-viewer/src/ui/networkResourceDetails.tsx b/packages/trace-viewer/src/ui/networkResourceDetails.tsx index 9805d42c6f..0c4af4e969 100644 --- a/packages/trace-viewer/src/ui/networkResourceDetails.tsx +++ b/packages/trace-viewer/src/ui/networkResourceDetails.tsx @@ -120,7 +120,7 @@ const ResponseTab: React.FunctionComponent<{ const BodyTab: React.FunctionComponent<{ resource: ResourceSnapshot; }> = ({ resource }) => { - const [responseBody, setResponseBody] = React.useState<{ dataUrl?: string, text?: string, mimeType?: string, font?: BinaryData } | null>(null); + const [responseBody, setResponseBody] = React.useState<{ dataUrl?: string, text?: string, mimeType?: string, font?: BufferSource } | null>(null); React.useEffect(() => { const readResources = async () => { @@ -158,7 +158,7 @@ const BodyTab: React.FunctionComponent<{ }; const FontPreview: React.FunctionComponent<{ - font: BinaryData; + font: BufferSource; }> = ({ font }) => { const [isError, setIsError] = React.useState(false); diff --git a/packages/web/src/components/treeView.tsx b/packages/web/src/components/treeView.tsx index 242e834ede..14f2e35195 100644 --- a/packages/web/src/components/treeView.tsx +++ b/packages/web/src/components/treeView.tsx @@ -180,7 +180,7 @@ export function TreeView({ const itemData = treeItems.get(child as T); return itemData && Date: Fri, 13 Dec 2024 15:28:06 +0000 Subject: [PATCH 68/85] feat(trace-viewer): render iframe canvas in trace viewer (#33809) --- .../trace/recorder/snapshotterInjected.ts | 12 +-- .../trace-viewer/src/sw/snapshotRenderer.ts | 87 +++++++++++++++---- tests/library/snapshotter.spec.ts | 6 +- tests/library/trace-viewer.spec.ts | 6 +- 4 files changed, 87 insertions(+), 24 deletions(-) diff --git a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts index 4d575ef0e9..4730c3e5dd 100644 --- a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts +++ b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts @@ -438,13 +438,13 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript: expectValue(value); attrs[kSelectedAttribute] = value; } - if (nodeName === 'CANVAS') { - const boundingRect = (element as HTMLCanvasElement).getBoundingClientRect(); + if (nodeName === 'CANVAS' || nodeName === 'IFRAME' || nodeName === 'FRAME') { + const boundingRect = (element as HTMLElement).getBoundingClientRect(); const value = JSON.stringify({ - left: boundingRect.left / window.innerWidth, - top: boundingRect.top / window.innerHeight, - right: boundingRect.right / window.innerWidth, - bottom: boundingRect.bottom / window.innerHeight + left: boundingRect.left, + top: boundingRect.top, + right: boundingRect.right, + bottom: boundingRect.bottom }); expectValue(kBoundingRectAttribute); expectValue(value); diff --git a/packages/trace-viewer/src/sw/snapshotRenderer.ts b/packages/trace-viewer/src/sw/snapshotRenderer.ts index 93363878ab..41c23ffa9c 100644 --- a/packages/trace-viewer/src/sw/snapshotRenderer.ts +++ b/packages/trace-viewer/src/sw/snapshotRenderer.ts @@ -152,7 +152,7 @@ export class SnapshotRenderer { const html = prefix + [ // Hide the document in order to prevent flickering. We will unhide once script has processed shadow. '', - `` + `` ].join('') + result.join(''); return { value: html, size: html.length }; }); @@ -236,10 +236,39 @@ function snapshotNodes(snapshot: FrameSnapshot): NodeSnapshot[] { return (snapshot as any)._nodes; } -function snapshotScript(...targetIds: (string | undefined)[]) { - function applyPlaywrightAttributes(unwrapPopoutUrl: (url: string) => string, ...targetIds: (string | undefined)[]) { +type ViewportSize = { width: number, height: number }; +type BoundingRect = { left: number, top: number, right: number, bottom: number }; +type FrameBoundingRectsInfo = { + viewport: ViewportSize; + frames: WeakMap; +}; + +declare global { + interface Window { + __playwright_frame_bounding_rects__: FrameBoundingRectsInfo; + } +} + +function snapshotScript(viewport: ViewportSize, ...targetIds: (string | undefined)[]) { + function applyPlaywrightAttributes(unwrapPopoutUrl: (url: string) => string, viewport: ViewportSize, ...targetIds: (string | undefined)[]) { const isUnderTest = new URLSearchParams(location.search).has('isUnderTest'); + // info to recursively compute canvas position relative to the top snapshot frame. + // Before rendering each iframe, its parent extracts the '__playwright_canvas_render_info__' attribute + // value and keeps in this variable. It can then remove the attribute and render the element, + // which will eventually trigger the same process inside the iframe recursively. + // When there's a canvas to render, we iterate over its ancestor frames to compute + // its position relative to the top snapshot frame. + const frameBoundingRectsInfo = { + viewport, + frames: new WeakMap(), + }; + window['__playwright_frame_bounding_rects__'] = frameBoundingRectsInfo; + const kPointerWarningTitle = 'Recorded click position in absolute coordinates did not' + ' match the center of the clicked element. This is likely due to a difference between' + ' the test runner and the trace viewer operating systems.'; @@ -249,6 +278,10 @@ function snapshotScript(...targetIds: (string | undefined)[]) { const targetElements: Element[] = []; const canvasElements: HTMLCanvasElement[] = []; + let topSnapshotWindow: Window = window; + while (topSnapshotWindow !== topSnapshotWindow.parent && !topSnapshotWindow.location.pathname.match(/\/page@[a-z0-9]+$/)) + topSnapshotWindow = topSnapshotWindow.parent; + const visit = (root: Document | ShadowRoot) => { // Collect all scrolled elements for later use. for (const e of root.querySelectorAll(`[__playwright_scroll_top_]`)) @@ -288,6 +321,11 @@ function snapshotScript(...targetIds: (string | undefined)[]) { } for (const iframe of root.querySelectorAll('iframe, frame')) { + const boundingRectJson = iframe.getAttribute('__playwright_bounding_rect__'); + iframe.removeAttribute('__playwright_bounding_rect__'); + const boundingRect = boundingRectJson ? JSON.parse(boundingRectJson) : undefined; + if (boundingRect) + frameBoundingRectsInfo.frames.set(iframe, { boundingRect, scrollLeft: 0, scrollTop: 0 }); const src = iframe.getAttribute('__playwright_src__'); if (!src) { iframe.setAttribute('src', 'data:text/html,'); @@ -339,16 +377,20 @@ function snapshotScript(...targetIds: (string | undefined)[]) { for (const element of scrollTops) { element.scrollTop = +element.getAttribute('__playwright_scroll_top_')!; element.removeAttribute('__playwright_scroll_top_'); + if (frameBoundingRectsInfo.frames.has(element)) + frameBoundingRectsInfo.frames.get(element)!.scrollTop = element.scrollTop; } for (const element of scrollLefts) { element.scrollLeft = +element.getAttribute('__playwright_scroll_left_')!; element.removeAttribute('__playwright_scroll_left_'); + if (frameBoundingRectsInfo.frames.has(element)) + frameBoundingRectsInfo.frames.get(element)!.scrollLeft = element.scrollTop; } document.styleSheets[0].disabled = true; const search = new URL(window.location.href).searchParams; - const isTopFrame = window.location.pathname.match(/\/page@[a-z0-9]+$/); + const isTopFrame = window === topSnapshotWindow; if (search.get('pointX') && search.get('pointY')) { const pointX = +search.get('pointX')!; @@ -419,16 +461,6 @@ function snapshotScript(...targetIds: (string | undefined)[]) { context.fillRect(0, 0, canvas.width, canvas.height); } - - if (!isTopFrame) { - for (const canvas of canvasElements) { - const context = canvas.getContext('2d')!; - drawCheckerboard(context, canvas); - canvas.title = `Playwright displays canvas contents on a best-effort basis. It doesn't support canvas elements inside an iframe yet. If this impacts your workflow, please open an issue so we can prioritize.`; - } - return; - } - const img = new Image(); img.onload = () => { for (const canvas of canvasElements) { @@ -446,6 +478,31 @@ function snapshotScript(...targetIds: (string | undefined)[]) { continue; } + let currWindow: Window = window; + while (currWindow !== topSnapshotWindow) { + const iframe = currWindow.frameElement!; + currWindow = currWindow.parent; + + const iframeInfo = currWindow['__playwright_frame_bounding_rects__']?.frames.get(iframe); + if (!iframeInfo?.boundingRect) + break; + + const leftOffset = iframeInfo.boundingRect.left - iframeInfo.scrollLeft; + const topOffset = iframeInfo.boundingRect.top - iframeInfo.scrollTop; + + boundingRect.left += leftOffset; + boundingRect.top += topOffset; + boundingRect.right += leftOffset; + boundingRect.bottom += topOffset; + } + + const { width, height } = topSnapshotWindow['__playwright_frame_bounding_rects__'].viewport; + + boundingRect.left = boundingRect.left / width; + boundingRect.top = boundingRect.top / height; + boundingRect.right = boundingRect.right / width; + boundingRect.bottom = boundingRect.bottom / height; + const partiallyUncaptured = boundingRect.right > 1 || boundingRect.bottom > 1; const fullyUncaptured = boundingRect.left > 1 || boundingRect.top > 1; if (fullyUncaptured) { @@ -483,7 +540,7 @@ function snapshotScript(...targetIds: (string | undefined)[]) { window.addEventListener('DOMContentLoaded', onDOMContentLoaded); } - return `\n(${applyPlaywrightAttributes.toString()})(${unwrapPopoutUrl.toString()}${targetIds.map(id => `, "${id}"`).join('')})`; + return `\n(${applyPlaywrightAttributes.toString()})(${unwrapPopoutUrl.toString()}, ${JSON.stringify(viewport)}${targetIds.map(id => `, "${id}"`).join('')})`; } diff --git a/tests/library/snapshotter.spec.ts b/tests/library/snapshotter.spec.ts index 7ada643dcc..3e9cfce46f 100644 --- a/tests/library/snapshotter.spec.ts +++ b/tests/library/snapshotter.spec.ts @@ -268,10 +268,12 @@ it.describe('snapshots', () => { }); }); -function distillSnapshot(snapshot, distillTarget = true) { +function distillSnapshot(snapshot, options: { distillTarget: boolean, distillBoundingRect: boolean } = { distillTarget: true, distillBoundingRect: true }) { let { html } = snapshot.render(); - if (distillTarget) + if (options.distillTarget) html = html.replace(/\s__playwright_target__="[^"]+"/g, ''); + if (options.distillBoundingRect) + html = html.replace(/\s__playwright_bounding_rect__="[^"]+"/g, ''); return html .replace(/