Merge branch 'microsoft:main' into main

This commit is contained in:
JacksonLei123 2024-11-22 16:36:28 -05:00 committed by GitHub
commit f742e94bb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 820 additions and 263 deletions

View file

@ -1,11 +0,0 @@
{
"name": "Playwright",
"image": "mcr.microsoft.com/playwright:next",
"postCreateCommand": "npm install && npm run build && apt-get update && apt-get install -y software-properties-common && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - && add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" && apt-get install -y docker-ce-cli",
"settings": {
"terminal.integrated.shell.linux": "/bin/bash"
},
"runArgs": [
"-v", "/var/run/docker.sock:/var/run/docker.sock"
]
}

View file

@ -1,6 +1,6 @@
# 🎭 Playwright
[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-132.0.6834.6-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-132.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![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) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-132.0.6834.15-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-132.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-18.2-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![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 <!-- GEN:chromium-version -->132.0.6834.6<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->132.0.6834.15<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->18.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->132.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |

View file

@ -93,11 +93,20 @@ Element is considered stable when it has maintained the same bounding box for at
## Enabled
Element is considered enabled unless it is a `<button>`, `<select>`, `<input>` or `<textarea>` with a `disabled` property.
Element is considered enabled when it is **not disabled**.
Element is **disabled** when:
- it is a `<button>`, `<select>`, `<input>`, `<textarea>`, `<option>` or `<optgroup>` with a `[disabled]` attribute;
- it is a `<button>`, `<select>`, `<input>`, `<textarea>`, `<option>` or `<optgroup>` that is a part of a `<fieldset>` with a `[disabled]` attribute;
- it is a descendant of an element with `[aria-disabled=true]` attribute.
## Editable
Element is considered editable when it is [enabled] and does not have `readonly` property set.
Element is considered editable when it is [enabled] and is **not readonly**.
Element is **readonly** when:
- it is a `<select>`, `<input>` or `<textarea>` with a `[readonly]` attribute;
- it has an `[aria-readonly=true]` attribute and an aria role that [supports it](https://w3c.github.io/aria/#aria-readonly).
## Receives Events

View file

@ -1483,7 +1483,7 @@ Boolean disabled = await page.GetByRole(AriaRole.Button).IsDisabledAsync();
* since: v1.14
- returns: <[boolean]>
Returns whether the element is [editable](../actionability.md#editable).
Returns whether the element is [editable](../actionability.md#editable). If the target element is not an `<input>`, `<textarea>`, `<select>`, `[contenteditable]` and does not have a role allowing `[aria-readonly]`, this method throws an error.
:::warning[Asserting editable state]
If you need to assert that an element is editable, prefer [`method: LocatorAssertions.toBeEditable`] to avoid flakiness. See [assertions guide](../test-assertions.md) for more details.

View file

@ -442,6 +442,23 @@ Expected options currently selected.
### option: LocatorAssertions.NotToHaveValues.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.23
## async method: LocatorAssertions.NotToMatchAriaSnapshot
* since: v1.49
* langs: python
The opposite of [`method: LocatorAssertions.toMatchAriaSnapshot`].
### param: LocatorAssertions.NotToMatchAriaSnapshot.expected
* since: v1.49
- `expected` <string>
### option: LocatorAssertions.NotToMatchAriaSnapshot.timeout = %%-js-assertions-timeout-%%
* since: v1.49
### option: LocatorAssertions.NotToMatchAriaSnapshot.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.49
## async method: LocatorAssertions.toBeAttached
* since: v1.33
@ -2122,7 +2139,7 @@ await expect(page.locator('body')).toMatchAriaSnapshot(`
```
```python async
await page.goto('https://demo.playwright.dev/todomvc/')
await page.goto("https://demo.playwright.dev/todomvc/")
await expect(page.locator('body')).to_match_aria_snapshot('''
- heading "todos"
- textbox "What needs to be done?"
@ -2130,7 +2147,7 @@ await expect(page.locator('body')).to_match_aria_snapshot('''
```
```python sync
page.goto('https://demo.playwright.dev/todomvc/')
page.goto("https://demo.playwright.dev/todomvc/")
expect(page.locator('body')).to_match_aria_snapshot('''
- heading "todos"
- textbox "What needs to be done?"

View file

@ -302,10 +302,10 @@ await test.step('Log in', async () => {
```java
// All actions between group and groupEnd
// will be shown in the trace viewer as a group.
page.context().tracing.group("Open Playwright.dev > API");
page.context().tracing().group("Open Playwright.dev > API");
page.navigate("https://playwright.dev/");
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("API")).click();
page.context().tracing.groupEnd();
page.context().tracing().groupEnd();
```
```python sync
@ -329,10 +329,10 @@ await page.context.tracing.group_end()
```csharp
// All actions between GroupAsync and GroupEndAsync
// will be shown in the trace viewer as a group.
await Page.Context().Tracing.GroupAsync("Open Playwright.dev > API");
await Page.Context.Tracing.GroupAsync("Open Playwright.dev > API");
await Page.GotoAsync("https://playwright.dev/");
await Page.GetByRole(AriaRole.Link, new() { Name = "API" }).ClickAsync();
await Page.Context().Tracing.GroupEndAsync();
await Page.Context.Tracing.GroupEndAsync();
```
### param: Tracing.group.name

View file

@ -1,36 +1,142 @@
---
id: aria-snapshots
title: "Aria snapshots"
title: "Snapshot testing"
---
import LiteYouTube from '@site/src/components/LiteYouTube';
## Overview
In Playwright, aria snapshots provide a YAML representation of the accessibility tree of a page.
These snapshots can be stored and compared later to verify if the page structure remains consistent or meets defined
expectations.
With the Playwright Snapshot testing you can assert the accessibility tree of a page against a predefined snapshot template.
```js
await page.goto('https://playwright.dev/');
await expect(page.getByRole('banner')).toMatchAriaSnapshot(`
- banner:
- heading /Playwright enables reliable end-to-end/ [level=1]
- link "Get started"
- link "Star microsoft/playwright on GitHub"
- link /[\\d]+k\\+ stargazers on GitHub/
`);
```
```python sync
page.goto('https://playwright.dev/')
expect(page.query_selector('banner')).to_match_aria_snapshot("""
- banner:
- heading /Playwright enables reliable end-to-end/ [level=1]
- link "Get started"
- link "Star microsoft/playwright on GitHub"
- link /[\\d]+k\\+ stargazers on GitHub/
""")
```
```python async
await page.goto('https://playwright.dev/')
await expect(page.query_selector('banner')).to_match_aria_snapshot("""
- banner:
- heading /Playwright enables reliable end-to-end/ [level=1]
- link "Get started"
- link "Star microsoft/playwright on GitHub"
- link /[\\d]+k\\+ stargazers on GitHub/
""")
```
```java
page.navigate("https://playwright.dev/");
assertThat(page.locator("banner")).matchesAriaSnapshot("""
- banner:
- heading /Playwright enables reliable end-to-end/ [level=1]
- link "Get started"
- link "Star microsoft/playwright on GitHub"
- link /[\\d]+k\\+ stargazers on GitHub/
""");
```
```csharp
await page.GotoAsync("https://playwright.dev/");
await Expect(page.Locator("banner")).ToMatchAriaSnapshotAsync(@"
- banner:
- heading ""Playwright enables reliable end-to-end testing for modern web apps."" [level=1]
- link ""Get started""
- link ""Star microsoft/playwright on GitHub""
- link /[\\d]+k\\+ stargazers on GitHub/
");
```
<LiteYouTube
id="P4R6hnsE0UY"
title="Getting started with ARIA Snapshots"
/>
## Assertion testing vs Snapshot testing
Snapshot testing and assertion testing serve different purposes in test automation:
### Assertion testing
Assertion testing is a targeted approach where you assert specific values or conditions about elements or components. For instance, with Playwright, [`method: LocatorAssertions.toHaveText`]
verifies that an element contains the expected text, and [`method: LocatorAssertions.toHaveValue`]
confirms that an input field has the expected value.
Assertion tests are specific and generally check the current state of an element or property
against an expected, predefined state.
They work well for predictable, single-value checks but are limited in scope when testing the
broader structure or variations.
**Advantages**
- **Clarity**: The intent of the test is explicit and easy to understand.
- **Specificity**: Tests focus on particular aspects of functionality, making them more robust
against unrelated changes.
- **Debugging**: Failures provide targeted feedback, pointing directly to the problematic aspect.
**Disadvantages**
- **Verbose for complex outputs**: Writing assertions for complex data structures or large outputs
can be cumbersome and error-prone.
- **Maintenance overhead**: As code evolves, manually updating assertions can be time-consuming.
### Snapshot testing
Snapshot testing captures a “snapshot” or representation of the entire
state of an element, component, or data at a given moment, which is then saved for future
comparisons. When re-running tests, the current state is compared to the snapshot, and if there
are differences, the test fails. This approach is especially useful for complex or dynamic
structures, where manually asserting each detail would be too time-consuming. Snapshot testing
is broader and more holistic than assertion testing, allowing you to track more complex changes over time.
**Advantages**
- **Simplifies complex outputs**: For example, testing a UI component's rendered output can be tedious with traditional assertions. Snapshots capture the entire output for easy comparison.
- **Quick Feedback loop**: Developers can easily spot unintended changes in the output.
- **Encourages consistency**: Helps maintain consistent output as code evolves.
**Disadvantages**
- **Over-Reliance**: It can be tempting to accept changes to snapshots without fully understanding
them, potentially hiding bugs.
- **Granularity**: Large snapshots may be hard to interpret when differences arise, especially
if minor changes affect large portions of the output.
- **Suitability**: Not ideal for highly dynamic content where outputs change frequently or
unpredictably.
### When to use
- **Snapshot testing** is ideal for:
- UI testing of whole pages and components.
- Broad structural checks for complex UI components.
- Regression testing for outputs that rarely change structure.
- **Assertion testing** is ideal for:
- Core logic validation.
- Computed value testing.
- Fine-grained tests requiring precise conditions.
By combining snapshot testing for broad, structural checks and assertion testing for specific functionality, you can achieve a well-rounded testing strategy.
## Aria snapshots
In Playwright, aria snapshots provide a YAML representation of the accessibility tree of a page.
These snapshots can be stored and compared later to verify if the page structure remains consistent or meets defined
expectations.
The YAML format describes the hierarchical structure of accessible elements on the page, detailing **roles**, **attributes**, **values**, and **text content**.
The structure follows a tree-like syntax, where each node represents an accessible element, and indentation indicates
nested elements.
Following is a simple example of an aria snapshot for the playwright.dev homepage:
```yaml
- banner:
- heading /Playwright enables reliable/ [level=1]
- link "Get started"
- link "Star microsoft/playwright on GitHub"
- main:
- img "Browsers (Chromium, Firefox, WebKit)"
- heading "Any browser • Any platform • One API"
```
Each accessible element in the tree is represented as a YAML node:
```yaml
@ -67,19 +173,19 @@ await expect(page.locator('body')).toMatchAriaSnapshot(`
```
```python sync
page.locator("body").to_match_aria_snapshot("""
expect(page.locator("body")).to_match_aria_snapshot("""
- heading "title"
""")
```
```python async
await page.locator("body").to_match_aria_snapshot("""
await expect(page.locator("body")).to_match_aria_snapshot("""
- heading "title"
""")
```
```java
page.locator("body").expect().toMatchAriaSnapshot("""
assertThat(page.locator("body")).matchesAriaSnapshot("""
- heading "title"
""");
```

View file

@ -132,7 +132,7 @@ Browser builds for Firefox and WebKit are built for the [glibc](https://en.wikip
You can use the [.NET install script](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script) in order to install different SDK versions:
```bash
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --install-dir /usr/share/dotnet --channel 6.0
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --install-dir /usr/share/dotnet --channel 9.0
```
## Build your own image

View file

@ -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#mstest) or [NUnit base classes](./test-runners.md#nunit) 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 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.
1. Start by creating a new project with `dotnet new`. This will create the `PlaywrightTests` directory which includes a `UnitTest1.cs` file:

View file

@ -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#mstest) and [NUnit base classes](https://playwright.dev/dotnet/docs/test-runners#nunit) for writing end-to-end tests.
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.
* [Documentation](https://playwright.dev/dotnet/docs/intro)
* [GitHub repo](https://github.com/microsoft/playwright-dotnet)

View file

@ -5,7 +5,7 @@ title: "Getting started - Library"
## Introduction
Playwright can either be used with the [MSTest](./test-runners.md#mstest) or [NUnit](./test-runners.md#nunit), 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](./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.
## Usage

View file

@ -5,6 +5,91 @@ toc_max_heading_level: 2
---
## Version 1.49
### Aria snapshots
New assertion [`method: LocatorAssertions.toMatchAriaSnapshot`] verifies page structure by comparing to an expected accessibility tree, represented as YAML.
```csharp
await page.GotoAsync("https://playwright.dev");
await Expect(page.Locator("body")).ToMatchAriaSnapshotAsync(@"
- banner:
- heading /Playwright enables reliable/ [level=1]
- link ""Get started""
- link ""Star microsoft/playwright on GitHub""
- main:
- img ""Browsers (Chromium, Firefox, WebKit)""
- heading ""Any browser • Any platform • One API""
");
```
You can generate this assertion with [Test Generator](./codegen) or by calling [`method: Locator.ariaSnapshot`].
Learn more in the [aria snapshots guide](./aria-snapshots).
### Tracing groups
New method [`method: Tracing.group`] allows you to visually group actions in the trace viewer.
```csharp
// All actions between GroupAsync and GroupEndAsync
// will be shown in the trace viewer as a group.
await Page.Context.Tracing.GroupAsync("Open Playwright.dev > API");
await Page.GotoAsync("https://playwright.dev/");
await Page.GetByRole(AriaRole.Link, new() { Name = "API" }).ClickAsync();
await Page.Context.Tracing.GroupEndAsync();
```
### Breaking: `chrome` and `msedge` channels switch to new headless mode
This change affects you if you're using one of the following channels in your `playwright.config.ts`:
- `chrome`, `chrome-dev`, `chrome-beta`, or `chrome-canary`
- `msedge`, `msedge-dev`, `msedge-beta`, or `msedge-canary`
After updating to Playwright v1.49, run your test suite. If it still passes, you're good to go. If not, you will probably need to update your snapshots, and adapt some of your test code around PDF viewers and extensions. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for more details.
### Try new Chromium headless
You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell):
> New Headless on the other hand is the real Chrome browser, and is thus more authentic, reliable, and offers more features. This makes it more suitable for high-accuracy end-to-end web app testing or browser extension testing.
See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for the list of possible breakages you could encounter and more details on Chromium headless. Please file an issue if you see any problems after opting in.
```xml csharp title="runsettings.xml"
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<Playwright>
<BrowserName>chromium</BrowserName>
<LaunchOptions>
<Channel>chromium</Channel>
</LaunchOptions>
</Playwright>
</RunSettings>
```
```bash csharp
dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Channel=chromium
```
### Miscellaneous
- There will be no more updates for WebKit on Ubuntu 20.04 and Debian 11. We recommend updating your OS to a later version.
- `<canvas>` elements inside a snapshot now draw a preview.
### Browser Versions
- Chromium 131.0.6778.33
- Mozilla Firefox 132.0
- WebKit 18.2
This version was also tested against the following stable channels:
- Google Chrome 130
- Microsoft Edge 130
## Version 1.48
### WebSocket routing

View file

@ -4,6 +4,79 @@ title: "Release notes"
toc_max_heading_level: 2
---
## Version 1.49
### Aria snapshots
New assertion [`method: LocatorAssertions.toMatchAriaSnapshot`] verifies page structure by comparing to an expected accessibility tree, represented as YAML.
```java
page.navigate("https://playwright.dev");
assertThat(page.locator("body")).matchesAriaSnapshot("""
- banner:
- heading /Playwright enables reliable/ [level=1]
- link "Get started"
- link "Star microsoft/playwright on GitHub"
- main:
- img "Browsers (Chromium, Firefox, WebKit)"
- heading "Any browser • Any platform • One API"
""");
```
You can generate this assertion with [Test Generator](./codegen) or by calling [`method: Locator.ariaSnapshot`].
Learn more in the [aria snapshots guide](./aria-snapshots).
### Tracing groups
New method [`method: Tracing.group`] allows you to visually group actions in the trace viewer.
```java
// All actions between group and groupEnd
// will be shown in the trace viewer as a group.
page.context().tracing().group("Open Playwright.dev > API");
page.navigate("https://playwright.dev/");
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("API")).click();
page.context().tracing().groupEnd();
```
### Breaking: `chrome` and `msedge` channels switch to new headless mode
This change affects you if you're using one of the following channels in your `playwright.config.ts`:
- `chrome`, `chrome-dev`, `chrome-beta`, or `chrome-canary`
- `msedge`, `msedge-dev`, `msedge-beta`, or `msedge-canary`
After updating to Playwright v1.49, run your test suite. If it still passes, you're good to go. If not, you will probably need to update your snapshots, and adapt some of your test code around PDF viewers and extensions. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for more details.
### Try new Chromium headless
You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell):
> New Headless on the other hand is the real Chrome browser, and is thus more authentic, reliable, and offers more features. This makes it more suitable for high-accuracy end-to-end web app testing or browser extension testing.
See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for the list of possible breakages you could encounter and more details on Chromium headless. Please file an issue if you see any problems after opting in.
```java
Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setChannel("chromium"));
```
### Miscellaneous
- There will be no more updates for WebKit on Ubuntu 20.04 and Debian 11. We recommend updating your OS to a later version.
- `<canvas>` elements inside a snapshot now draw a preview.
### Browser Versions
- Chromium 131.0.6778.33
- Mozilla Firefox 132.0
- WebKit 18.2
This version was also tested against the following stable channels:
- Google Chrome 130
- Microsoft Edge 130
## Version 1.48
### WebSocket routing

View file

@ -4,6 +4,80 @@ title: "Release notes"
toc_max_heading_level: 2
---
## Version 1.49
### Aria snapshots
New assertion [`method: LocatorAssertions.toMatchAriaSnapshot`] verifies page structure by comparing to an expected accessibility tree, represented as YAML.
```python
page.goto("https://playwright.dev")
expect(page.locator('body')).to_match_aria_snapshot('''
- banner:
- heading /Playwright enables reliable/ [level=1]
- link "Get started"
- link "Star microsoft/playwright on GitHub"
- main:
- img "Browsers (Chromium, Firefox, WebKit)"
- heading "Any browser • Any platform • One API"
''')
```
You can generate this assertion with [Test Generator](./codegen) or by calling [`method: Locator.ariaSnapshot`].
Learn more in the [aria snapshots guide](./aria-snapshots).
### Tracing groups
New method [`method: Tracing.group`] allows you to visually group actions in the trace viewer.
```python
# All actions between group and group_end
# will be shown in the trace viewer as a group.
page.context.tracing.group("Open Playwright.dev > API")
page.goto("https://playwright.dev/")
page.get_by_role("link", name="API").click()
page.context.tracing.group_end()
```
### Breaking: `chrome` and `msedge` channels switch to new headless mode
This change affects you if you're using one of the following channels in your `playwright.config.ts`:
- `chrome`, `chrome-dev`, `chrome-beta`, or `chrome-canary`
- `msedge`, `msedge-dev`, `msedge-beta`, or `msedge-canary`
After updating to Playwright v1.49, run your test suite. If it still passes, you're good to go. If not, you will probably need to update your snapshots, and adapt some of your test code around PDF viewers and extensions. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for more details.
### Try new Chromium headless
You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell):
> New Headless on the other hand is the real Chrome browser, and is thus more authentic, reliable, and offers more features. This makes it more suitable for high-accuracy end-to-end web app testing or browser extension testing.
See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for the list of possible breakages you could encounter and more details on Chromium headless. Please file an issue if you see any problems after opting in.
```bash python
pytest test_login.py --browser-channel chromium
```
### Miscellaneous
- There will be no more updates for WebKit on Ubuntu 20.04 and Debian 11. We recommend updating your OS to a later version.
- `<canvas>` elements inside a snapshot now draw a preview.
- Python 3.8 is not supported anymore.
### Browser Versions
- Chromium 131.0.6778.33
- Mozilla Firefox 132.0
- WebKit 18.2
This version was also tested against the following stable channels:
- Google Chrome 130
- Microsoft Edge 130
## Version 1.48
### WebSocket routing

View file

@ -5,19 +5,57 @@ 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](#mstest) and [NUnit](#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 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.
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
isolated between the tests.
## MSTest
<Tabs
groupId="test-runners"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
]
}>
<TabItem value="nunit">
Playwright provides base classes to write tests with NUnit via the [`Microsoft.Playwright.NUnit`](https://www.nuget.org/packages/Microsoft.Playwright.NUnit) package.
</TabItem>
<TabItem value="mstest">
Playwright provides base classes to write tests with MSTest via the [`Microsoft.Playwright.MSTest`](https://www.nuget.org/packages/Microsoft.Playwright.MSTest) package.
</TabItem>
</Tabs>
Check out the [installation guide](./intro.md) to get started.
### Running MSTest tests in Parallel
## Running tests in Parallel
<Tabs
groupId="test-runners"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
]
}>
<TabItem value="nunit">
By default NUnit will run all test files in parallel, while running tests inside each file sequentially (`ParallelScope.Self`). It will create as many processes as there are cores on the host system. You can adjust this behavior using the NUnit.NumberOfTestWorkers parameter.
Only `ParallelScope.Self` is supported.
For CPU-bound tests, we recommend using as many workers as there are cores on your system, divided by 2. For IO-bound tests you can use as many workers as you have cores.
```bash
dotnet test -- NUnit.NumberOfTestWorkers=5
```
</TabItem>
<TabItem value="mstest">
By default MSTest will run all classes in parallel, while running tests inside each class sequentially (`ExecutionScope.ClassLevel`). It will create as many processes as there are cores on the host system. You can adjust this behavior by using the following CLI parameter or using a `.runsettings` file, see below.
Running tests in parallel at the method level (`ExecutionScope.MethodLevel`) is not supported.
@ -26,7 +64,58 @@ Running tests in parallel at the method level (`ExecutionScope.MethodLevel`) is
dotnet test --settings:.runsettings -- MSTest.Parallelize.Workers=4
```
### Customizing [BrowserContext] options
</TabItem>
</Tabs>
## Customizing [BrowserContext] options
<Tabs
groupId="test-runners"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
]
}>
<TabItem value="nunit">
To customize context options, you can override the `ContextOptions` method of your test class derived from `Microsoft.Playwright.MSTest.PageTest` or `Microsoft.Playwright.MSTest.ContextTest`. See the following example:
```csharp
using Microsoft.Playwright.NUnit;
namespace PlaywrightTests;
[Parallelizable(ParallelScope.Self)]
[TestFixture]
public class MyTest : PageTest
{
[Test]
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",
};
}
}
```
</TabItem>
<TabItem value="mstest">
To customize context options, you can override the `ContextOptions` method of your test class derived from `Microsoft.Playwright.MSTest.PageTest` or `Microsoft.Playwright.MSTest.ContextTest`. See the following example:
@ -65,7 +154,11 @@ public class ExampleTest : PageTest
```
### Customizing [Browser]/launch options
</TabItem>
</Tabs>
## Customizing [Browser]/launch options
[Browser]/launch options can be overridden either using a run settings file or by setting the run settings options directly via the
CLI. See the following example:
@ -87,14 +180,55 @@ CLI. See the following example:
dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Headless=false Playwright.LaunchOptions.Channel=msedge
```
### Using Verbose API Logs
## Using Verbose API Logs
When you have enabled the [verbose API log](./debug.md#verbose-api-logs), via the `DEBUG` environment variable, you will see the messages in the standard error stream. In MSTest, within Visual Studio, that will be the `Tests` pane of the `Output` window. It will also be displayed in the `Test Log` for each test.
When you have enabled the [verbose API log](./debug.md#verbose-api-logs), via the `DEBUG` environment variable, you will see the messages in the standard error stream. Within Visual Studio, that will be the `Tests` pane of the `Output` window. It will also be displayed in the `Test Log` for each test.
### Using the .runsettings file
## Using the .runsettings file
When running tests from Visual Studio, you can take advantage of the `.runsettings` file. The following shows a reference of the supported values.
<Tabs
groupId="test-runners"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
]
}>
<TabItem value="nunit">
For example, to specify the number of workers you can use `NUnit.NumberOfTestWorkers` or to enable `DEBUG` logs `RunConfiguration.EnvironmentVariables`.
```xml
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<!-- NUnit adapter -->
<NUnit>
<NumberOfTestWorkers>24</NumberOfTestWorkers>
</NUnit>
<!-- General run configuration -->
<RunConfiguration>
<EnvironmentVariables>
<!-- For debugging selectors, it's recommend to set the following environment variable -->
<DEBUG>pw:api</DEBUG>
</EnvironmentVariables>
</RunConfiguration>
<!-- Playwright -->
<Playwright>
<BrowserName>chromium</BrowserName>
<ExpectTimeout>5000</ExpectTimeout>
<LaunchOptions>
<Headless>false</Headless>
<Channel>msedge</Channel>
</LaunchOptions>
</Playwright>
</RunSettings>
```
</TabItem>
<TabItem value="mstest">
For example, to specify the number of workers, you can use `MSTest.Parallelize.Workers`. You can also enable `DEBUG` logs using `RunConfiguration.EnvironmentVariables`.
```xml
@ -125,131 +259,30 @@ For example, to specify the number of workers, you can use `MSTest.Parallelize.W
</RunSettings>
```
### Base MSTest classes for Playwright
</TabItem>
</Tabs>
## Base classes for Playwright
<Tabs
groupId="test-runners"
defaultValue="mstest"
values={[
{label: 'MSTest', value: 'mstest'},
{label: 'NUnit', value: 'nunit'},
]
}>
<TabItem value="nunit">
There are a few base classes available to you in `Microsoft.Playwright.NUnit` namespace:
</TabItem>
<TabItem value="mstest">
There are a few base classes available to you in `Microsoft.Playwright.MSTest` namespace:
|Test |Description|
|--------------|-----------|
|PageTest |Each test gets a fresh copy of a web [Page] created in its own unique [BrowserContext]. Extending this class is the simplest way of writing a fully-functional Playwright test.<br></br><br></br>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.|
|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.<br></br><br></br>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.|
## NUnit
Playwright provides base classes to write tests with NUnit via the [`Microsoft.Playwright.NUnit`](https://www.nuget.org/packages/Microsoft.Playwright.NUnit) package.
Check out the [installation guide](./intro.md) to get started.
### Running NUnit tests in Parallel
By default NUnit will run all test files in parallel, while running tests inside each file sequentially (`ParallelScope.Self`). It will create as many processes as there are cores on the host system. You can adjust this behavior using the NUnit.NumberOfTestWorkers parameter.
Only `ParallelScope.Self` is supported.
For CPU-bound tests, we recommend using as many workers as there are cores on your system, divided by 2. For IO-bound tests you can use as many workers as you have cores.
```bash
dotnet test -- NUnit.NumberOfTestWorkers=5
```
### Customizing [BrowserContext] options
To customize context options, you can override the `ContextOptions` method of your test class derived from `Microsoft.Playwright.MSTest.PageTest` or `Microsoft.Playwright.MSTest.ContextTest`. See the following example:
```csharp
using Microsoft.Playwright.NUnit;
namespace PlaywrightTests;
[Parallelizable(ParallelScope.Self)]
[TestFixture]
public class MyTest : PageTest
{
[Test]
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",
};
}
}
```
### Customizing [Browser]/launch options
[Browser]/launch options can be overridden either using a run settings file or by setting the run settings options directly via the
CLI. See the following example:
```xml
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<Playwright>
<BrowserName>chromium</BrowserName>
<LaunchOptions>
<Headless>false</Headless>
<Channel>msedge</Channel>
</LaunchOptions>
</Playwright>
</RunSettings>
```
```bash
dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Headless=false Playwright.LaunchOptions.Channel=msedge
```
### Using Verbose API Logs
When you have enabled the [verbose API log](./debug.md#verbose-api-logs), via the `DEBUG` environment variable, you will see the messages in the standard error stream. In NUnit, within Visual Studio, that will be the `Tests` pane of the `Output` window. It will also be displayed in the `Test Log` for each test.
### Using the .runsettings file
When running tests from Visual Studio, you can take advantage of the `.runsettings` file. The following shows a reference of the supported values.
For example, to specify the amount of workers you can use `NUnit.NumberOfTestWorkers` or to enable `DEBUG` logs `RunConfiguration.EnvironmentVariables`.
```xml
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<!-- NUnit adapter -->
<NUnit>
<NumberOfTestWorkers>24</NumberOfTestWorkers>
</NUnit>
<!-- General run configuration -->
<RunConfiguration>
<EnvironmentVariables>
<!-- For debugging selectors, it's recommend to set the following environment variable -->
<DEBUG>pw:api</DEBUG>
</EnvironmentVariables>
</RunConfiguration>
<!-- Playwright -->
<Playwright>
<BrowserName>chromium</BrowserName>
<ExpectTimeout>5000</ExpectTimeout>
<LaunchOptions>
<Headless>false</Headless>
<Channel>msedge</Channel>
</LaunchOptions>
</Playwright>
</RunSettings>
```
### Base NUnit classes for Playwright
There are a few base classes available to you in `Microsoft.Playwright.NUnit` namespace:
</TabItem>
</Tabs>
|Test |Description|
|--------------|-----------|

View file

@ -50,6 +50,8 @@ window.onload = () => {
ReactDOM.createRoot(document.querySelector('#root')!).render(<ReportLoader />);
};
const kPlaywrightReportStorageForHMR = 'playwrightReportStorageForHMR';
class ZipReport implements LoadedReport {
private _entries = new Map<string, zip.Entry>();
private _json!: HTMLReport;
@ -58,8 +60,20 @@ class ZipReport implements LoadedReport {
const zipURI = await new Promise<string>(resolve => {
if (window.playwrightReportBase64)
return resolve(window.playwrightReportBase64);
window.addEventListener('message', event => event.source === window.opener && resolve(event.data), { once: true });
window.opener.postMessage('ready', '*');
if (window.opener) {
window.addEventListener('message', event => {
if (event.source === window.opener) {
localStorage.setItem(kPlaywrightReportStorageForHMR, event.data);
resolve(event.data);
}
}, { once: true });
window.opener.postMessage('ready', '*');
} else {
const oldReport = localStorage.getItem(kPlaywrightReportStorageForHMR);
if (oldReport)
return resolve(oldReport);
alert('couldnt find report, something with HMR is broken');
}
});
const zipReader = new zipjs.ZipReader(new zipjs.Data64URIReader(zipURI), { useWebWorkers: false });

View file

@ -3,21 +3,21 @@
"browsers": [
{
"name": "chromium",
"revision": "1149",
"revision": "1150",
"installByDefault": true,
"browserVersion": "132.0.6834.6"
"browserVersion": "132.0.6834.15"
},
{
"name": "chromium-headless-shell",
"revision": "1149",
"revision": "1150",
"installByDefault": true,
"browserVersion": "132.0.6834.6"
"browserVersion": "132.0.6834.15"
},
{
"name": "chromium-tip-of-tree",
"revision": "1279",
"revision": "1280",
"installByDefault": false,
"browserVersion": "133.0.6846.0"
"browserVersion": "133.0.6850.0"
},
{
"name": "firefox",
@ -33,9 +33,11 @@
},
{
"name": "webkit",
"revision": "2105",
"revision": "2108",
"installByDefault": true,
"revisionOverrides": {
"debian11-x64": "2105",
"debian11-arm64": "2105",
"mac10.14": "1446",
"mac10.15": "1616",
"mac11": "1816",

View file

@ -731,6 +731,10 @@ class FrameSession {
if (!frame)
return; // Subtree may be already gone due to renderer/browser race.
this._page._frameManager.removeChildFramesRecursively(frame);
for (const [contextId, context] of this._contextIdToContext) {
if (context.frame === frame)
this._onExecutionContextDestroyed(contextId);
}
const frameSession = new FrameSession(this._crPage, session, targetId, this);
this._crPage._sessions.set(targetId, frameSession);
frameSession._initialize(false).catch(e => e);

View file

@ -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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 Safari/537.36",
"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",
"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.6 Safari/537.36",
"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",
"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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 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.15 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.6 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 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.6 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 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.6 Mobile Safari/537.36",
"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",
"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.6 Mobile Safari/537.36",
"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",
"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.6 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 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.6 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 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.6 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 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.6 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 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.6 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.15 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.6 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.15 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.6 Safari/537.36",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 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.6 Safari/537.36 Edg/132.0.6834.6",
"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",
"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.6 Safari/537.36",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.15 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.6 Safari/537.36 Edg/132.0.6834.6",
"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",
"screen": {
"width": 1920,
"height": 1080

View file

@ -942,7 +942,7 @@ export class Frame extends SdkObject {
origin(): string | undefined {
if (!this._url.startsWith('http'))
return;
return network.parsedURL(this._url)?.origin;
return network.parseURL(this._url)?.origin;
}
parentFrame(): Frame | null {

View file

@ -257,7 +257,7 @@ export class HarTracer {
const page = request.frame()?._page;
if (this._page && page !== this._page)
return;
const url = network.parsedURL(request.url());
const url = network.parseURL(request.url());
if (!url)
return;

View file

@ -29,7 +29,7 @@ import type { CSSComplexSelectorList } from '../../utils/isomorphic/cssParser';
import { generateSelector, type GenerateSelectorOptions } from './selectorGenerator';
import type * as channels from '@protocol/channels';
import { Highlight } from './highlight';
import { getChecked, getAriaDisabled, getAriaRole, getElementAccessibleName, getElementAccessibleDescription } from './roleUtils';
import { getChecked, getAriaDisabled, getAriaRole, getElementAccessibleName, getElementAccessibleDescription, getReadonly } from './roleUtils';
import { kLayoutSelectorNames, type LayoutSelectorName, layoutSelectorScore } from './layoutSelectorUtils';
import { asLocator } from '../../utils/isomorphic/locatorGenerators';
import type { Language } from '../../utils/isomorphic/locatorGenerators';
@ -626,9 +626,12 @@ export class InjectedScript {
if (state === 'enabled')
return !disabled;
const editable = !(['INPUT', 'TEXTAREA', 'SELECT'].includes(element.nodeName) && element.hasAttribute('readonly'));
if (state === 'editable')
return !disabled && editable;
if (state === 'editable') {
const readonly = getReadonly(element);
if (readonly === 'error')
throw this.createStacklessError('Element is not an <input>, <textarea>, <select> or [contenteditable] and does not have a role allowing [aria-readonly]');
return !disabled && !readonly;
}
if (state === 'checked' || state === 'unchecked') {
const need = state === 'checked';

View file

@ -860,6 +860,21 @@ export function getChecked(element: Element, allowMixed: boolean): boolean | 'mi
return 'error';
}
// https://w3c.github.io/aria/#aria-readonly
const kAriaReadonlyRoles = ['checkbox', 'combobox', 'grid', 'gridcell', 'listbox', 'radiogroup', 'slider', 'spinbutton', 'textbox', 'columnheader', 'rowheader', 'searchbox', 'switch', 'treegrid'];
export function getReadonly(element: Element): boolean | 'error' {
const tagName = elementSafeTagName(element);
// https://www.w3.org/TR/wai-aria-1.2/#aria-checked
// https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(tagName))
return element.hasAttribute('readonly');
if (kAriaReadonlyRoles.includes(getAriaRole(element) || ''))
return element.getAttribute('aria-readonly') === 'true';
if ((element as HTMLElement).isContentEditable)
return false;
return 'error';
}
export const kAriaPressedRoles = ['button'];
export function getAriaPressed(element: Element): boolean | 'mixed' {
// https://www.w3.org/TR/wai-aria-1.2/#aria-pressed

View file

@ -74,7 +74,7 @@ export function rewriteCookies(cookies: channels.SetNetworkCookie[]): channels.S
});
}
export function parsedURL(url: string): URL | null {
export function parseURL(url: string): URL | null {
try {
return new URL(url);
} catch (e) {

View file

@ -329,7 +329,7 @@ export const deps: any = {
'libgstreamer-gl1.0-0',
'libgstreamer-plugins-base1.0-0',
'libgstreamer1.0-0',
'libgtk-3-0',
'libgtk-4-1',
'libgudev-1.0-0',
'libharfbuzz-icu0',
'libharfbuzz0b',
@ -400,6 +400,7 @@ export const deps: any = {
'libgsttag-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgstvideo-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgtk-3.so.0': 'libgtk-3-0',
'libgtk-4.so.1': 'libgtk-4-1',
'libgudev-1.0.so.0': 'libgudev-1.0-0',
'libharfbuzz-icu.so.0': 'libharfbuzz-icu0',
'libharfbuzz.so.0': 'libharfbuzz0b',
@ -544,7 +545,7 @@ export const deps: any = {
'libgstreamer-plugins-bad1.0-0',
'libgstreamer-plugins-base1.0-0',
'libgstreamer1.0-0',
'libgtk-3-0t64',
'libgtk-4-1',
'libharfbuzz-icu0',
'libharfbuzz0b',
'libhyphen0',
@ -621,6 +622,7 @@ export const deps: any = {
'libgsttag-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgstvideo-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgtk-3.so.0': 'libgtk-3-0t64',
'libgtk-4.so.1': 'libgtk-4-1',
'libharfbuzz-icu.so.0': 'libharfbuzz-icu0',
'libharfbuzz.so.0': 'libharfbuzz0b',
'libhyphen.so.0': 'libhyphen0',
@ -967,7 +969,7 @@ export const deps: any = {
'libgstreamer-gl1.0-0',
'libgstreamer-plugins-base1.0-0',
'libgstreamer1.0-0',
'libgtk-3-0',
'libgtk-4-1',
'libgudev-1.0-0',
'libharfbuzz-icu0',
'libharfbuzz0b',
@ -1028,6 +1030,7 @@ export const deps: any = {
'libXfixes.so.3': 'libxfixes3',
'libxkbcommon.so.0': 'libxkbcommon0',
'libXrandr.so.2': 'libxrandr2',
'libgtk-4.so.1': 'libgtk-4-1',
}
},
};

View file

@ -48,6 +48,7 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript:
const kCustomElementsAttribute = '__playwright_custom_elements__';
const kCurrentSrcAttribute = '__playwright_current_src__';
const kBoundingRectAttribute = '__playwright_bounding_rect__';
const kPopoverOpenAttribute = '__playwright_popover_open_';
// Symbols for our own info on Nodes/StyleSheets.
const kSnapshotFrameId = Symbol('__playwright_snapshot_frameid_');
@ -449,6 +450,12 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript:
expectValue(value);
attrs[kBoundingRectAttribute] = value;
}
if ((element as HTMLElement).popover && (element as HTMLElement).matches && (element as HTMLElement).matches(':popover-open')) {
const value = 'true';
expectValue(kPopoverOpenAttribute);
expectValue(value);
attrs[kPopoverOpenAttribute] = value;
}
if (element.scrollTop) {
expectValue(kScrollTopAttribute);
expectValue(element.scrollTop);

View file

@ -43,7 +43,7 @@ export class WebKit extends BrowserType {
override doRewriteStartupLog(error: ProtocolError): ProtocolError {
if (!error.logs)
return error;
if (error.logs.includes('cannot open display'))
if (error.logs.includes('Failed to open display') || error.logs.includes('cannot open display'))
error.logs = '\n' + wrapInASCIIBox(kNoXServerRunningError, 1);
return error;
}

View file

@ -107,19 +107,15 @@ export function urlMatches(baseURL: string | undefined, urlString: string, match
match = globToRegex(match);
if (isRegExp(match))
return match.test(urlString);
if (typeof match === 'string' && match === urlString)
return true;
const url = parsedURL(urlString);
const url = parseURL(urlString);
if (!url)
return false;
if (typeof match === 'string')
return url.pathname === match;
if (typeof match !== 'function')
throw new Error('url parameter should be string, RegExp or function');
return match(url);
}
function parsedURL(url: string): URL | null {
function parseURL(url: string): URL | null {
try {
return new URL(url);
} catch (e) {

View file

@ -13680,7 +13680,9 @@ export interface Locator {
}): Promise<boolean>;
/**
* Returns whether the element is [editable](https://playwright.dev/docs/actionability#editable).
* Returns whether the element is [editable](https://playwright.dev/docs/actionability#editable). If the target element is not an `<input>`,
* `<textarea>`, `<select>`, `[contenteditable]` and does not have a role allowing `[aria-readonly]`, this method
* throws an error.
*
* **NOTE** If you need to assert that an element is editable, prefer
* [expect(locator).toBeEditable([options])](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-be-editable)

View file

@ -54,6 +54,7 @@ export async function applySuggestedRebaselines(config: FullConfigInternal, repo
const patches: string[] = [];
const files: string[] = [];
const gitCache = new Map<string, string | null>();
for (const fileName of [...suggestedRebaselines.keys()].sort()) {
const source = await fs.promises.readFile(fileName, 'utf8');
@ -96,9 +97,16 @@ export async function applySuggestedRebaselines(config: FullConfigInternal, repo
for (const range of ranges)
result = result.substring(0, range.start) + range.newText + result.substring(range.end);
const relativeName = path.relative(process.cwd(), fileName);
files.push(relativeName);
patches.push(createPatch(relativeName, source, result));
if (process.env.PWTEST_UPDATE_SNAPSHOTS === 'overwrite') {
await fs.promises.writeFile(fileName, result);
} else if (process.env.PWTEST_UPDATE_SNAPSHOTS === 'manual') {
await fs.promises.writeFile(fileName, applyPatchWithConflictMarkers(source, result));
} else {
const gitFolder = findGitRoot(path.dirname(fileName), gitCache);
const relativeName = path.relative(gitFolder || process.cwd(), fileName);
files.push(relativeName);
patches.push(createPatch(relativeName, source, result));
}
}
const patchFile = path.join(project.project.outputDir, 'rebaselines.patch');
@ -119,3 +127,62 @@ function createPatch(fileName: string, before: string, after: string) {
...text.split('\n').slice(4)
].join('\n');
}
function findGitRoot(dir: string, cache: Map<string, string | null>): string | null {
const result = cache.get(dir);
if (result !== undefined)
return result;
const gitPath = path.join(dir, '.git');
if (fs.existsSync(gitPath) && fs.lstatSync(gitPath).isDirectory()) {
cache.set(dir, dir);
return dir;
}
const parentDir = path.dirname(dir);
if (dir === parentDir) {
cache.set(dir, null);
return null;
}
const parentResult = findGitRoot(parentDir, cache);
cache.set(dir, parentResult);
return parentResult;
}
function applyPatchWithConflictMarkers(oldText: string, newText: string) {
const diffResult = diff.diffLines(oldText, newText);
let result = '';
let conflict = false;
diffResult.forEach(part => {
if (part.added) {
if (conflict) {
result += part.value;
result += '>>>>>>> SNAPSHOT\n';
conflict = false;
} else {
result += '<<<<<<< HEAD\n';
result += part.value;
result += '=======\n';
conflict = true;
}
} else if (part.removed) {
result += '<<<<<<< HEAD\n';
result += part.value;
result += '=======\n';
conflict = true;
} else {
if (conflict) {
result += '>>>>>>> SNAPSHOT\n';
conflict = false;
}
result += part.value;
}
});
if (conflict)
result += '>>>>>>> SNAPSHOT\n';
return result;
}

View file

@ -1849,7 +1849,7 @@ export interface FullConfig<TestArgs = {}, WorkerArgs = {}> {
export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted';
type TestDetailsAnnotation = {
export type TestDetailsAnnotation = {
type: string;
description?: string;
};

View file

@ -270,6 +270,13 @@ function snapshotScript(...targetIds: (string | undefined)[]) {
(element as HTMLOptionElement).selected = element.getAttribute('__playwright_selected_') === 'true';
element.removeAttribute('__playwright_selected_');
}
for (const element of root.querySelectorAll(`[__playwright_popover_open_]`)) {
try {
(element as HTMLElement).showPopover();
} catch {
}
element.removeAttribute('__playwright_popover_open_');
}
for (const targetId of targetIds) {
for (const target of root.querySelectorAll(`[__playwright_target__="${targetId}"]`)) {

View file

@ -61,6 +61,7 @@ export const androidTest = baseTest.extend<PageTestFixtures & AndroidTestFixture
isElectron: [false, { scope: 'worker' }],
electronMajorVersion: [0, { scope: 'worker' }],
isWebView2: [false, { scope: 'worker' }],
isHeadlessShell: [false, { scope: 'worker' }],
androidDevice: async ({ androidDeviceWorker }, use) => {
await closeAllActivities(androidDeviceWorker);

View file

@ -35,6 +35,7 @@ export const electronTest = baseTest.extend<TraceViewerFixtures>(traceViewerFixt
isAndroid: [false, { scope: 'worker' }],
isElectron: [true, { scope: 'worker' }],
isWebView2: [false, { scope: 'worker' }],
isHeadlessShell: [false, { scope: 'worker' }],
launchElectronApp: async ({ playwright }, use) => {
// This env prevents 'Electron Security Policy' console message.

View file

@ -101,8 +101,9 @@ it('should change document.activeElement', async ({ page, server }) => {
expect(active).toEqual(['INPUT', 'TEXTAREA']);
});
it('should not affect screenshots', async ({ page, server, browserName, headless, isWindows, isHeadlessShell }) => {
it('should not affect screenshots', async ({ page, server, browserName, headless, isWindows, isLinux, isHeadlessShell }) => {
it.skip(browserName === 'webkit' && isWindows && !headless, 'WebKit/Windows/headed has a larger minimal viewport. See https://github.com/microsoft/playwright/issues/22616');
it.skip(browserName === 'webkit' && isLinux && !headless, 'WebKit headed has a larger minimal viewport on gtk4.');
it.skip(browserName === 'firefox' && !headless, 'Firefox headed produces a different image');
it.fixme(browserName === 'chromium' && !isHeadlessShell, 'https://github.com/microsoft/playwright/issues/33330');

View file

@ -1572,3 +1572,20 @@ test('should show only one pointer with multilevel iframes', async ({ page, runA
await expect.soft(snapshotFrame.frameLocator('iframe').locator('x-pw-pointer')).not.toBeAttached();
await expect.soft(snapshotFrame.frameLocator('iframe').frameLocator('iframe').locator('x-pw-pointer')).toBeVisible();
});
test('should show a popover', async ({ runAndTrace, page, server }) => {
const traceViewer = await runAndTrace(async () => {
await page.setContent(`
<button popovertarget="pop">Click me</button>
<article id="pop" popover="auto">
<div>I'm a popover</div>
</article>
`);
await page.getByRole('button').click();
await expect(page.locator('div')).toBeVisible();
});
const snapshot = await traceViewer.snapshotFrame('expect.toBeVisible');
const popover = snapshot.locator('#pop');
await expect.poll(() => popover.evaluate(e => e.matches(':popover-open'))).toBe(true);
});

View file

@ -45,7 +45,7 @@ it.describe('element screenshot', () => {
expect(screenshot).toMatchSnapshot('screenshot-element-bounding-box.png');
});
it('should take into account padding and border', async ({ page }) => {
it('should take into account padding and border', async ({ page, isLinux, headless, browserName }) => {
await page.setViewportSize({ width: 500, height: 500 });
await page.setContent(`
<div style="height: 14px">oooo</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 B

After

Width:  |  Height:  |  Size: 332 B

View file

@ -138,6 +138,13 @@ test.describe('toBeEditable', () => {
const locator = page.locator('input');
await expect(locator).not.toBeEditable({ editable: false });
});
test('throws', async ({ page }) => {
await page.setContent('<button>');
const locator = page.locator('button');
const error = await expect(locator).toBeEditable().catch(e => e);
expect(error.message).toContain('Element is not an <input>, <textarea>, <select> or [contenteditable] and does not have a role allowing [aria-readonly]');
});
});
test.describe('toBeEnabled', () => {

View file

@ -119,7 +119,15 @@ it('isEnabled and isDisabled should work', async ({ page }) => {
});
it('isEditable should work', async ({ page }) => {
await page.setContent(`<input id=input1 disabled><textarea></textarea><input id=input2>`);
await page.setContent(`
<input id=input1 disabled>
<textarea></textarea>
<input id=input2>
<div contenteditable="true"></div>
<span id=span1 role=textbox aria-readonly=true></span>
<span id=span2 role=textbox></span>
<button>button</button>
`);
await page.$eval('textarea', t => t.readOnly = true);
const input1 = page.locator('#input1');
expect(await input1.isEditable()).toBe(false);
@ -130,6 +138,11 @@ it('isEditable should work', async ({ page }) => {
const textarea = page.locator('textarea');
expect(await textarea.isEditable()).toBe(false);
expect(await page.isEditable('textarea')).toBe(false);
expect(await page.locator('div').isEditable()).toBe(true);
expect(await page.locator('#span1').isEditable()).toBe(false);
expect(await page.locator('#span2').isEditable()).toBe(true);
const error = await page.locator('button').isEditable().catch(e => e);
expect(error.message).toContain('Element is not an <input>, <textarea>, <select> or [contenteditable] and does not have a role allowing [aria-readonly]');
});
it('isChecked should work', async ({ page }) => {

View file

@ -238,8 +238,8 @@ it('should fill elements with existing value and selection', async ({ page, serv
it('should throw nice error without injected script stack when element is not an <input>', async ({ page, server }) => {
let error = null;
await page.goto(server.PREFIX + '/input/textarea.html');
await page.fill('body', '').catch(e => error = e);
await page.setContent(`<select><option>value1</option></select>`);
await page.fill('select', '').catch(e => error = e);
expect(error.message).toContain('page.fill: Error: Element is not an <input>, <textarea> or [contenteditable] element\nCall log:');
});

View file

@ -280,12 +280,13 @@ it.describe('page screenshot', () => {
expect(screenshot).toMatchSnapshot('screenshot-clip-odd-size.png');
});
it('should work for canvas', async ({ page, server, isElectron, isMac, isLinux, macVersion, browserName, headless }) => {
it('should work for canvas', async ({ page, server, isElectron, isMac, isLinux, macVersion, browserName, isHeadlessShell, headless }) => {
it.fixme(isElectron && isMac, 'Fails on the bots');
it.fixme(browserName === 'webkit' && isLinux && !headless, 'WebKit has slightly different corners on gtk4.');
await page.setViewportSize({ width: 500, height: 500 });
await page.goto(server.PREFIX + '/screenshots/canvas.html');
const screenshot = await page.screenshot();
if ((!headless && browserName === 'chromium' && isMac && os.arch() === 'arm64' && macVersion >= 14) ||
if ((!isHeadlessShell && browserName === 'chromium' && isMac && os.arch() === 'arm64' && macVersion >= 14) ||
(browserName === 'webkit' && isLinux && os.arch() === 'x64'))
expect(screenshot).toMatchSnapshot('screenshot-canvas-with-accurate-corners.png');
else

View file

@ -36,4 +36,5 @@ export type PageWorkerFixtures = {
isAndroid: boolean;
isElectron: boolean;
isWebView2: boolean;
isHeadlessShell: boolean;
};

View file

@ -26,6 +26,7 @@ function trimPatch(patch: string) {
test('should update snapshot with the update-snapshots flag with multiple projects', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'.git/marker': '',
'playwright.config.ts': `
export default { projects: [{ name: 'p1' }, { name: 'p2' }] };
`,
@ -73,6 +74,7 @@ test('should update snapshot with the update-snapshots flag with multiple projec
test('should update missing snapshots', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'.git/marker': '',
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
@ -116,6 +118,7 @@ test('should update missing snapshots', async ({ runInlineTest }, testInfo) => {
test('should generate baseline with regex', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'.git/marker': '',
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
@ -172,6 +175,7 @@ test('should generate baseline with regex', async ({ runInlineTest }, testInfo)
test('should generate baseline with special characters', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'.git/marker': '',
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
@ -241,6 +245,7 @@ test('should generate baseline with special characters', async ({ runInlineTest
test('should update missing snapshots in tsx', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'.git/marker': '',
'playwright.config.ts': playwrightCtConfigText,
'playwright/index.html': `<script type="module" src="./index.ts"></script>`,
'playwright/index.ts': ``,
@ -286,6 +291,7 @@ test('should update missing snapshots in tsx', async ({ runInlineTest }, testInf
test('should update multiple files', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'.git/marker': '',
'playwright.config.ts': playwrightCtConfigText,
'playwright/index.html': `<script type="module" src="./index.ts"></script>`,
'playwright/index.ts': ``,
@ -365,6 +371,7 @@ diff --git a/src/button-2.test.tsx b/src/button-2.test.tsx
test('should generate baseline for input values', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'.git/marker': '',
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
@ -400,6 +407,7 @@ test('should generate baseline for input values', async ({ runInlineTest }, test
test('should not update snapshots when locator did not match', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'.git/marker': '',
'a.spec.ts': `
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {

View file

@ -32,6 +32,7 @@ export const webView2Test = baseTest.extend<TraceViewerFixtures>(traceViewerFixt
isElectron: [false, { scope: 'worker' }],
electronMajorVersion: [0, { scope: 'worker' }],
isWebView2: [true, { scope: 'worker' }],
isHeadlessShell: [false, { scope: 'worker' }],
browser: [async ({ playwright }, use, testInfo) => {
const cdpPort = 10000 + testInfo.workerIndex;

View file

@ -65,7 +65,7 @@ export interface FullConfig<TestArgs = {}, WorkerArgs = {}> {
export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted';
type TestDetailsAnnotation = {
export type TestDetailsAnnotation = {
type: string;
description?: string;
};