Compare commits

...

16 commits

Author SHA1 Message Date
Dmitry Gozman a051ceb8e1
chore: mark 1.43.1 (#30354) 2024-04-12 10:19:02 -07:00
Max Schmitt 3ab466e1d3
cherry-pick(#30312): fix(ui-mode): do not loose run information after writing into testDir (#30328)
Partially reverts https://github.com/microsoft/playwright/pull/30008
that started to reset all test results upon listing tests, including the
test that did just run and triggered re-listing.

https://github.com/microsoft/playwright/issues/30300.
2024-04-11 23:40:07 +02:00
Playwright Service 35468cfaaa
cherry-pick(#30342): Revert "fix(reuse): reset Origin Private File System API (#29921)" (#30344)
This PR cherry-picks the following commits:

- 96053ed0b5

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-04-11 20:28:25 +02:00
Max Schmitt 5332639114 cherry-pick(#30263): docs: add v1.43 release notes for language ports 2024-04-09 09:09:56 +02:00
Max Schmitt c729a7b86d cherry-pick(#30232): docs: fix C# and python snippets 2024-04-09 09:09:32 +02:00
Max Schmitt 7748e219a1 cherry-pick(#30210): docs: update context.backgroundPage event examples 2024-04-09 09:09:14 +02:00
Max Schmitt acb6ff1d84 cherry-pick(#30200): docs(java,csharp): add BrowserContext.backgroundPage(s) 2024-04-09 09:08:56 +02:00
Dmitry Gozman 7c7f8ac9fa
cherry-pick(#30227): chore(deps): bump vite from 5.0.12 to 5.0.13 (#30254) 2024-04-04 12:25:32 -07:00
Max Schmitt 62d4dc9ebb cherry-pick(#30235): feat(chromium): roll to r1112 2024-04-04 09:59:36 +02:00
Pavel Feldman 79d477666f cherry-pick(#30226): chore: migrate to the testserver.initialize 2024-04-03 12:52:29 -07:00
Pavel Feldman 6b94231dcf cherry-pick(#30202): do not run setup tasks on test run via server 2024-04-01 15:32:58 -07:00
Pavel Feldman 0889736332 cherry-pick(#30185): chore: opt into stdio forwarding 2024-03-30 18:48:34 -07:00
Dmitry Gozman a01db3ffd7
chore: mark version 1.43.0 (#30183) 2024-03-29 12:54:53 -07:00
Pavel Feldman 1cd1239d62 cherry-pick(#30170): chore: hide internal commands 2024-03-28 12:19:58 -07:00
Max Schmitt 122ab67e34 cherry-pick(#30138): fix: UI Mode tags without theme applied
Trace Viewer theming is override based. By default you should have
something applied - an optional `dark-mode` class might be there too.
2024-03-27 16:47:16 +01:00
Pavel Feldman 75776dbc3e cherry-pick(#30135): chore: do not exit UI mode upon page reload 2024-03-26 15:51:25 -07:00
41 changed files with 434 additions and 273 deletions

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-124.0.6367.8-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-124.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-17.4-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop -->
[![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-124.0.6367.29-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-124.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-17.4-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop -->
## [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 -->124.0.6367.8<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->124.0.6367.29<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->124.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |

View file

@ -64,7 +64,6 @@ await context.CloseAsync();
## event: BrowserContext.backgroundPage
* since: v1.11
* langs: js, python
- argument: <[Page]>
:::note
@ -73,6 +72,12 @@ Only works with Chromium browser's persistent context.
Emitted when new background page is created in the context.
```java
context.onBackgroundPage(backgroundPage -> {
System.out.println(backgroundPage.url());
});
```
```js
const backgroundPage = await context.waitForEvent('backgroundpage');
```
@ -85,6 +90,14 @@ background_page = await context.wait_for_event("backgroundpage")
background_page = context.wait_for_event("backgroundpage")
```
```csharp
context.BackgroundPage += (_, backgroundPage) =>
{
Console.WriteLine(backgroundPage.Url);
};
```
## event: BrowserContext.close
* since: v1.8
- argument: <[BrowserContext]>
@ -441,7 +454,6 @@ Script to be evaluated in all pages in the browser context. Optional.
## method: BrowserContext.backgroundPages
* since: v1.11
* langs: js, python
- returns: <[Array]<[Page]>>
:::note

View file

@ -4,6 +4,50 @@ title: "Release notes"
toc_max_heading_level: 2
---
## Version 1.43
### New APIs
- Method [`method: BrowserContext.clearCookies`] now supports filters to remove only some cookies.
```csharp
// Clear all cookies.
await Context.ClearCookiesAsync();
// New: clear cookies with a particular name.
await Context.ClearCookiesAsync(new() { Name = "session-id" });
// New: clear cookies for a particular domain.
await Context.ClearCookiesAsync(new() { Domain = "my-origin.com" });
```
- New property [`method: Locator.contentFrame`] converts a [Locator] object to a [FrameLocator]. This can be useful when you have a [Locator] object obtained somewhere, and later on would like to interact with the content inside the frame.
```csharp
var locator = Page.Locator("iframe[name='embedded']");
// ...
var frameLocator = locator.ContentFrame;
await frameLocator.GetByRole(AriaRole.Button).ClickAsync();
```
- New property [`method: FrameLocator.owner`] converts a [FrameLocator] object to a [Locator]. This can be useful when you have a [FrameLocator] object obtained somewhere, and later on would like to interact with the `iframe` element.
```csharp
var frameLocator = page.FrameLocator("iframe[name='embedded']");
// ...
var locator = frameLocator.Owner;
await Expect(locator).ToBeVisibleAsync();
```
### Browser Versions
* Chromium 124.0.6367.8
* Mozilla Firefox 124.0
* WebKit 17.4
This version was also tested against the following stable channels:
* Google Chrome 123
* Microsoft Edge 123
## Version 1.42
### New Locator Handler

View file

@ -4,6 +4,50 @@ title: "Release notes"
toc_max_heading_level: 2
---
## Version 1.43
### New APIs
- Method [`method: BrowserContext.clearCookies`] now supports filters to remove only some cookies.
```java
// Clear all cookies.
context.clearCookies();
// New: clear cookies with a particular name.
context.clearCookies(new BrowserContext.ClearCookiesOptions().setName("session-id"));
// New: clear cookies for a particular domain.
context.clearCookies(new BrowserContext.ClearCookiesOptions().setDomain("my-origin.com"));
```
- New method [`method: Locator.contentFrame`] converts a [Locator] object to a [FrameLocator]. This can be useful when you have a [Locator] object obtained somewhere, and later on would like to interact with the content inside the frame.
```java
Locator locator = page.locator("iframe[name='embedded']");
// ...
FrameLocator frameLocator = locator.contentFrame();
frameLocator.getByRole(AriaRole.BUTTON).click();
```
- New method [`method: FrameLocator.owner`] converts a [FrameLocator] object to a [Locator]. This can be useful when you have a [FrameLocator] object obtained somewhere, and later on would like to interact with the `iframe` element.
```java
FrameLocator frameLocator = page.frameLocator("iframe[name='embedded']");
// ...
Locator locator = frameLocator.owner();
assertThat(locator).isVisible();
```
### Browser Versions
* Chromium 124.0.6367.8
* Mozilla Firefox 124.0
* WebKit 17.4
This version was also tested against the following stable channels:
* Google Chrome 123
* Microsoft Edge 123
## Version 1.42
### Experimental JUnit integration

View file

@ -4,6 +4,52 @@ title: "Release notes"
toc_max_heading_level: 2
---
## Version 1.43
### New APIs
- Method [`method: BrowserContext.clearCookies`] now supports filters to remove only some cookies.
```python
# Clear all cookies.
context.clear_cookies()
# New: clear cookies with a particular name.
context.clear_cookies(name="session-id")
# New: clear cookies for a particular domain.
context.clear_cookies(domain="my-origin.com")
```
- New method [`method: Locator.contentFrame`] converts a [Locator] object to a [FrameLocator]. This can be useful when you have a [Locator] object obtained somewhere, and later on would like to interact with the content inside the frame.
```python
locator = page.locator("iframe[name='embedded']")
# ...
frame_locator = locator.content_frame
frame_locator.getByRole("button").click()
```
- New method [`method: FrameLocator.owner`] converts a [FrameLocator] object to a [Locator]. This can be useful when you have a [FrameLocator] object obtained somewhere, and later on would like to interact with the `iframe` element.
```python
frame_locator = page.frame_locator("iframe[name='embedded']")
# ...
locator = frame_locator.owner
expect(locator).to_be_visible()
```
- Conda builds are now published for macOS-arm64 and Linux-arm64.
### Browser Versions
* Chromium 124.0.6367.8
* Mozilla Firefox 124.0
* WebKit 17.4
This version was also tested against the following stable channels:
* Google Chrome 123
* Microsoft Edge 123
## Version 1.42
### New Locator Handler

78
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "playwright-internal",
"version": "1.43.0-next",
"version": "1.43.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "playwright-internal",
"version": "1.43.0-next",
"version": "1.43.1",
"license": "Apache-2.0",
"workspaces": [
"packages/*"
@ -61,7 +61,7 @@
"socksv5": "0.0.6",
"ssim.js": "^3.5.0",
"typescript": "^5.3.2",
"vite": "^5.0.12",
"vite": "^5.0.13",
"ws": "^8.5.0",
"xml2js": "^0.5.0",
"yaml": "^2.2.2"
@ -7345,9 +7345,9 @@
}
},
"node_modules/vite": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz",
"integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==",
"version": "5.0.13",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.13.tgz",
"integrity": "sha512-/9ovhv2M2dGTuA+dY93B9trfyWMDRQw2jdVBhHNP6wr0oF34wG2i/N55801iZIpgUpnHDm4F/FabGQLyc+eOgg==",
"dependencies": {
"esbuild": "^0.19.3",
"postcss": "^8.4.32",
@ -8136,10 +8136,10 @@
}
},
"packages/playwright": {
"version": "1.43.0-next",
"version": "1.43.1",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
},
"bin": {
"playwright": "cli.js"
@ -8153,11 +8153,11 @@
},
"packages/playwright-browser-chromium": {
"name": "@playwright/browser-chromium",
"version": "1.43.0-next",
"version": "1.43.1",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
},
"engines": {
"node": ">=16"
@ -8165,11 +8165,11 @@
},
"packages/playwright-browser-firefox": {
"name": "@playwright/browser-firefox",
"version": "1.43.0-next",
"version": "1.43.1",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
},
"engines": {
"node": ">=16"
@ -8177,22 +8177,22 @@
},
"packages/playwright-browser-webkit": {
"name": "@playwright/browser-webkit",
"version": "1.43.0-next",
"version": "1.43.1",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
},
"engines": {
"node": ">=16"
}
},
"packages/playwright-chromium": {
"version": "1.43.0-next",
"version": "1.43.1",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
},
"bin": {
"playwright": "cli.js"
@ -8202,7 +8202,7 @@
}
},
"packages/playwright-core": {
"version": "1.43.0-next",
"version": "1.43.1",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
@ -8213,12 +8213,12 @@
},
"packages/playwright-ct-core": {
"name": "@playwright/experimental-ct-core",
"version": "1.43.0-next",
"version": "1.43.1",
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.43.0-next",
"playwright-core": "1.43.0-next",
"vite": "^5.0.12"
"playwright": "1.43.1",
"playwright-core": "1.43.1",
"vite": "^5.0.13"
},
"bin": {
"playwright": "cli.js"
@ -8229,10 +8229,10 @@
},
"packages/playwright-ct-react": {
"name": "@playwright/experimental-ct-react",
"version": "1.43.0-next",
"version": "1.43.1",
"license": "Apache-2.0",
"dependencies": {
"@playwright/experimental-ct-core": "1.43.0-next",
"@playwright/experimental-ct-core": "1.43.1",
"@vitejs/plugin-react": "^4.2.1"
},
"bin": {
@ -8244,10 +8244,10 @@
},
"packages/playwright-ct-react17": {
"name": "@playwright/experimental-ct-react17",
"version": "1.43.0-next",
"version": "1.43.1",
"license": "Apache-2.0",
"dependencies": {
"@playwright/experimental-ct-core": "1.43.0-next",
"@playwright/experimental-ct-core": "1.43.1",
"@vitejs/plugin-react": "^4.2.1"
},
"bin": {
@ -8259,10 +8259,10 @@
},
"packages/playwright-ct-solid": {
"name": "@playwright/experimental-ct-solid",
"version": "1.43.0-next",
"version": "1.43.1",
"license": "Apache-2.0",
"dependencies": {
"@playwright/experimental-ct-core": "1.43.0-next",
"@playwright/experimental-ct-core": "1.43.1",
"vite-plugin-solid": "^2.7.0"
},
"bin": {
@ -8277,10 +8277,10 @@
},
"packages/playwright-ct-svelte": {
"name": "@playwright/experimental-ct-svelte",
"version": "1.43.0-next",
"version": "1.43.1",
"license": "Apache-2.0",
"dependencies": {
"@playwright/experimental-ct-core": "1.43.0-next",
"@playwright/experimental-ct-core": "1.43.1",
"@sveltejs/vite-plugin-svelte": "^3.0.1"
},
"bin": {
@ -8295,10 +8295,10 @@
},
"packages/playwright-ct-vue": {
"name": "@playwright/experimental-ct-vue",
"version": "1.43.0-next",
"version": "1.43.1",
"license": "Apache-2.0",
"dependencies": {
"@playwright/experimental-ct-core": "1.43.0-next",
"@playwright/experimental-ct-core": "1.43.1",
"@vitejs/plugin-vue": "^4.2.1"
},
"bin": {
@ -8310,10 +8310,10 @@
},
"packages/playwright-ct-vue2": {
"name": "@playwright/experimental-ct-vue2",
"version": "1.43.0-next",
"version": "1.43.1",
"license": "Apache-2.0",
"dependencies": {
"@playwright/experimental-ct-core": "1.43.0-next",
"@playwright/experimental-ct-core": "1.43.1",
"@vitejs/plugin-vue2": "^2.2.0"
},
"bin": {
@ -8362,11 +8362,11 @@
}
},
"packages/playwright-firefox": {
"version": "1.43.0-next",
"version": "1.43.1",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
},
"bin": {
"playwright": "cli.js"
@ -8377,10 +8377,10 @@
},
"packages/playwright-test": {
"name": "@playwright/test",
"version": "1.43.0-next",
"version": "1.43.1",
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.43.0-next"
"playwright": "1.43.1"
},
"bin": {
"playwright": "cli.js"
@ -8390,11 +8390,11 @@
}
},
"packages/playwright-webkit": {
"version": "1.43.0-next",
"version": "1.43.1",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
},
"bin": {
"playwright": "cli.js"

View file

@ -1,7 +1,7 @@
{
"name": "playwright-internal",
"private": true,
"version": "1.43.0-next",
"version": "1.43.1",
"description": "A high-level API to automate web browsers",
"repository": {
"type": "git",
@ -99,7 +99,7 @@
"socksv5": "0.0.6",
"ssim.js": "^3.5.0",
"typescript": "^5.3.2",
"vite": "^5.0.12",
"vite": "^5.0.13",
"ws": "^8.5.0",
"xml2js": "^0.5.0",
"yaml": "^2.2.2"

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/browser-chromium",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "Playwright package that automatically installs Chromium",
"repository": {
"type": "git",
@ -27,6 +27,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/browser-firefox",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "Playwright package that automatically installs Firefox",
"repository": {
"type": "git",
@ -27,6 +27,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/browser-webkit",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "Playwright package that automatically installs WebKit",
"repository": {
"type": "git",
@ -27,6 +27,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "playwright-chromium",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "A high-level API to automate Chromium",
"repository": {
"type": "git",
@ -30,6 +30,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
}
}

View file

@ -3,9 +3,9 @@
"browsers": [
{
"name": "chromium",
"revision": "1110",
"revision": "1112",
"installByDefault": true,
"browserVersion": "124.0.6367.8"
"browserVersion": "124.0.6367.29"
},
{
"name": "chromium-tip-of-tree",

View file

@ -1,6 +1,6 @@
{
"name": "playwright-core",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "A high-level API to automate web browsers",
"repository": {
"type": "git",

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/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 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/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 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/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 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/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 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/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 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/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 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/124.0.6367.8 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 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/124.0.6367.8 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Safari/537.36",
"viewport": {
"width": 1138,
"height": 712
@ -978,7 +978,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 384,
"height": 640
@ -989,7 +989,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 384
@ -1000,7 +1000,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36 Edge/14.14263",
"viewport": {
"width": 640,
"height": 360
@ -1011,7 +1011,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36 Edge/14.14263",
"viewport": {
"width": 360,
"height": 640
@ -1022,7 +1022,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36 Edge/14.14263",
"viewport": {
"width": 360,
"height": 640
@ -1033,7 +1033,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36 Edge/14.14263",
"viewport": {
"width": 640,
"height": 360
@ -1044,7 +1044,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/124.0.6367.8 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Safari/537.36",
"viewport": {
"width": 800,
"height": 1280
@ -1055,7 +1055,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/124.0.6367.8 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Safari/537.36",
"viewport": {
"width": 1280,
"height": 800
@ -1066,7 +1066,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/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 384,
"height": 640
@ -1077,7 +1077,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/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 384
@ -1088,7 +1088,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 5": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 360,
"height": 640
@ -1099,7 +1099,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/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 360
@ -1110,7 +1110,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 412,
"height": 732
@ -1121,7 +1121,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 732,
"height": 412
@ -1132,7 +1132,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/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 412,
"height": 732
@ -1143,7 +1143,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/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 732,
"height": 412
@ -1154,7 +1154,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 412,
"height": 732
@ -1165,7 +1165,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 732,
"height": 412
@ -1176,7 +1176,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/124.0.6367.8 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Safari/537.36",
"viewport": {
"width": 600,
"height": 960
@ -1187,7 +1187,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/124.0.6367.8 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Safari/537.36",
"viewport": {
"width": 960,
"height": 600
@ -1242,7 +1242,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 411,
"height": 731
@ -1253,7 +1253,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 731,
"height": 411
@ -1264,7 +1264,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 411,
"height": 823
@ -1275,7 +1275,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 823,
"height": 411
@ -1286,7 +1286,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 393,
"height": 786
@ -1297,7 +1297,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/124.0.6367.8 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/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 786,
"height": 393
@ -1308,7 +1308,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 4": {
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 353,
"height": 745
@ -1319,7 +1319,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 4 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 745,
"height": 353
@ -1330,7 +1330,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 4a (5G)": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"screen": {
"width": 412,
"height": 892
@ -1345,7 +1345,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 4a (5G) landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"screen": {
"height": 892,
"width": 412
@ -1360,7 +1360,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 5": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"screen": {
"width": 393,
"height": 851
@ -1375,7 +1375,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 5 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"screen": {
"width": 851,
"height": 393
@ -1390,7 +1390,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 7": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"screen": {
"width": 412,
"height": 915
@ -1405,7 +1405,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 7 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"screen": {
"width": 915,
"height": 412
@ -1420,7 +1420,7 @@
"defaultBrowserType": "chromium"
},
"Moto G4": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 360,
"height": 640
@ -1431,7 +1431,7 @@
"defaultBrowserType": "chromium"
},
"Moto G4 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 360
@ -1442,7 +1442,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Chrome HiDPI": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Safari/537.36",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Safari/537.36",
"screen": {
"width": 1792,
"height": 1120
@ -1457,7 +1457,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Edge HiDPI": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Safari/537.36 Edg/124.0.6367.8",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Safari/537.36 Edg/124.0.6367.29",
"screen": {
"width": 1792,
"height": 1120
@ -1502,7 +1502,7 @@
"defaultBrowserType": "webkit"
},
"Desktop Chrome": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Safari/537.36",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Safari/537.36",
"screen": {
"width": 1920,
"height": 1080
@ -1517,7 +1517,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Edge": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.8 Safari/537.36 Edg/124.0.6367.8",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.29 Safari/537.36 Edg/124.0.6367.29",
"screen": {
"width": 1920,
"height": 1080

View file

@ -1692,17 +1692,6 @@ export class Frame extends SdkObject {
if (db.name)
indexedDB.deleteDatabase(db.name!);
}
// Clean StorageManager
const root = await navigator.storage.getDirectory();
const entries = await (root as any).entries();
// Manual loop instead of for await because in Firefox's utility context instanceof AsyncIterable is not working.
let entry = await entries.next();
while (!entry.done) {
const [name] = entry.value;
await root.removeEntry(name, { recursive: true });
entry = await entries.next();
}
}, { ls: newStorage?.localStorage }).catch(() => {});
}

View file

@ -222,14 +222,13 @@ class StdinServer implements Transport {
}
async dispatch(method: string, params: any) {
if (method === 'ready') {
if (method === 'initialize') {
if (this._traceUrl)
this._loadTrace(this._traceUrl);
}
}
onclose() {
gracefullyProcessExitDoNotHang(0);
}
sendEvent?: (method: string, params: any) => void;

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-core",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "Playwright Component Testing Helpers",
"repository": {
"type": "git",
@ -26,9 +26,9 @@
}
},
"dependencies": {
"playwright-core": "1.43.0-next",
"vite": "^5.0.12",
"playwright": "1.43.0-next"
"playwright-core": "1.43.1",
"vite": "^5.0.13",
"playwright": "1.43.1"
},
"bin": {
"playwright": "cli.js"

View file

@ -21,7 +21,7 @@ import { runDevServer } from './devServer';
export { program } from 'playwright/lib/program';
function addDevServerCommand(program: Command) {
const command = program.command('dev-server');
const command = program.command('dev-server', { hidden: true });
command.description('start dev server');
command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
command.action(options => {

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-react",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "Playwright Component Testing for React",
"repository": {
"type": "git",
@ -29,7 +29,7 @@
}
},
"dependencies": {
"@playwright/experimental-ct-core": "1.43.0-next",
"@playwright/experimental-ct-core": "1.43.1",
"@vitejs/plugin-react": "^4.2.1"
},
"bin": {

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-react17",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "Playwright Component Testing for React",
"repository": {
"type": "git",
@ -29,7 +29,7 @@
}
},
"dependencies": {
"@playwright/experimental-ct-core": "1.43.0-next",
"@playwright/experimental-ct-core": "1.43.1",
"@vitejs/plugin-react": "^4.2.1"
},
"bin": {

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-solid",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "Playwright Component Testing for Solid",
"repository": {
"type": "git",
@ -29,7 +29,7 @@
}
},
"dependencies": {
"@playwright/experimental-ct-core": "1.43.0-next",
"@playwright/experimental-ct-core": "1.43.1",
"vite-plugin-solid": "^2.7.0"
},
"devDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-svelte",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "Playwright Component Testing for Svelte",
"repository": {
"type": "git",
@ -29,7 +29,7 @@
}
},
"dependencies": {
"@playwright/experimental-ct-core": "1.43.0-next",
"@playwright/experimental-ct-core": "1.43.1",
"@sveltejs/vite-plugin-svelte": "^3.0.1"
},
"devDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-vue",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "Playwright Component Testing for Vue",
"repository": {
"type": "git",
@ -29,7 +29,7 @@
}
},
"dependencies": {
"@playwright/experimental-ct-core": "1.43.0-next",
"@playwright/experimental-ct-core": "1.43.1",
"@vitejs/plugin-vue": "^4.2.1"
},
"bin": {

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-vue2",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "Playwright Component Testing for Vue2",
"repository": {
"type": "git",
@ -29,7 +29,7 @@
}
},
"dependencies": {
"@playwright/experimental-ct-core": "1.43.0-next",
"@playwright/experimental-ct-core": "1.43.1",
"@vitejs/plugin-vue2": "^2.2.0"
},
"devDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "playwright-firefox",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "A high-level API to automate Firefox",
"repository": {
"type": "git",
@ -30,6 +30,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/test",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "A high-level API to automate web browsers",
"repository": {
"type": "git",
@ -30,6 +30,6 @@
},
"scripts": {},
"dependencies": {
"playwright": "1.43.0-next"
"playwright": "1.43.1"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "playwright-webkit",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "A high-level API to automate WebKit",
"repository": {
"type": "git",
@ -30,6 +30,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "playwright",
"version": "1.43.0-next",
"version": "1.43.1",
"description": "A high-level API to automate web browsers",
"repository": {
"type": "git",
@ -58,7 +58,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.43.0-next"
"playwright-core": "1.43.1"
},
"optionalDependencies": {
"fsevents": "2.3.2"

View file

@ -130,6 +130,7 @@ type TeleReporterReceiverOptions = {
};
export class TeleReporterReceiver {
public isListing = false;
private _rootSuite: TeleSuite;
private _options: TeleReporterReceiverOptions;
private _reporter: Partial<ReporterV2>;
@ -143,12 +144,6 @@ export class TeleReporterReceiver {
this._reporter = reporter;
}
reset() {
this._rootSuite.suites = [];
this._rootSuite.tests = [];
this._tests.clear();
}
dispatch(message: JsonEvent): Promise<void> | void {
const { method, params } = message;
if (method === 'onConfigure') {
@ -209,6 +204,28 @@ export class TeleReporterReceiver {
// Always update project in watch mode.
projectSuite._project = this._parseProject(project);
this._mergeSuitesInto(project.suites, projectSuite);
// Remove deleted tests when listing. Empty suites will be auto-filtered
// in the UI layer.
if (this.isListing) {
const testIds = new Set<string>();
const collectIds = (suite: JsonSuite) => {
suite.tests.map(t => t.testId).forEach(testId => testIds.add(testId));
suite.suites.forEach(collectIds);
};
project.suites.forEach(collectIds);
const filterTests = (suite: TeleSuite) => {
suite.tests = suite.tests.filter(t => {
if (testIds.has(t.id))
return true;
this._tests.delete(t.id);
return false;
});
suite.suites.forEach(filterTests);
};
filterTests(projectSuite);
}
}
private _onBegin() {

View file

@ -64,10 +64,7 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
});
const pingInterval = setInterval(() => this._sendMessage('ping').catch(() => {}), 30000);
this._connectedPromise = new Promise<void>((f, r) => {
this._ws.addEventListener('open', () => {
f();
this._ws.send(JSON.stringify({ id: -1, method: 'ready' }));
});
this._ws.addEventListener('open', () => f());
this._ws.addEventListener('error', r);
});
this._ws.addEventListener('close', () => {
@ -76,10 +73,6 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
});
}
connect() {
return this._connectedPromise;
}
private async _sendMessage(method: string, params?: any): Promise<any> {
const logForTest = (globalThis as any).__logForTest;
logForTest?.({ method, params });
@ -110,16 +103,16 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
this._onLoadTraceRequestedEmitter.fire(params);
}
async setSerializer(params: { serializer: string; }): Promise<void> {
await this._sendMessage('setSerializer', params);
async initialize(params: Parameters<TestServerInterface['initialize']>[0]): ReturnType<TestServerInterface['initialize']> {
await this._sendMessage('initialize', params);
}
async ping(params: Parameters<TestServerInterface['ping']>[0]): ReturnType<TestServerInterface['ping']> {
await this._sendMessage('ping');
await this._sendMessage('ping', params);
}
async pingNoReply(params: Parameters<TestServerInterface['ping']>[0]) {
this._sendMessageNoReply('ping');
this._sendMessageNoReply('ping', params);
}
async watch(params: Parameters<TestServerInterface['watch']>[0]): ReturnType<TestServerInterface['watch']> {
@ -130,10 +123,6 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
this._sendMessageNoReply('watch', params);
}
async watchTestDir(params: Parameters<TestServerInterface['watchTestDir']>[0]): ReturnType<TestServerInterface['watchTestDir']> {
await this._sendMessage('watchTestDir', params);
}
async open(params: Parameters<TestServerInterface['open']>[0]): ReturnType<TestServerInterface['open']> {
await this._sendMessage('open', params);
}
@ -151,19 +140,19 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
}
async checkBrowsers(params: Parameters<TestServerInterface['checkBrowsers']>[0]): ReturnType<TestServerInterface['checkBrowsers']> {
return await this._sendMessage('checkBrowsers');
return await this._sendMessage('checkBrowsers', params);
}
async installBrowsers(params: Parameters<TestServerInterface['installBrowsers']>[0]): ReturnType<TestServerInterface['installBrowsers']> {
await this._sendMessage('installBrowsers');
await this._sendMessage('installBrowsers', params);
}
async runGlobalSetup(params: Parameters<TestServerInterface['runGlobalSetup']>[0]): ReturnType<TestServerInterface['runGlobalSetup']> {
return await this._sendMessage('runGlobalSetup');
return await this._sendMessage('runGlobalSetup', params);
}
async runGlobalTeardown(params: Parameters<TestServerInterface['runGlobalTeardown']>[0]): ReturnType<TestServerInterface['runGlobalTeardown']> {
return await this._sendMessage('runGlobalTeardown');
return await this._sendMessage('runGlobalTeardown', params);
}
async listFiles(params: Parameters<TestServerInterface['listFiles']>[0]): ReturnType<TestServerInterface['listFiles']> {
@ -183,14 +172,21 @@ export class TestServerConnection implements TestServerInterface, TestServerInte
}
async stopTests(params: Parameters<TestServerInterface['stopTests']>[0]): ReturnType<TestServerInterface['stopTests']> {
await this._sendMessage('stopTests');
await this._sendMessage('stopTests', params);
}
stopTestsNoReply(params: Parameters<TestServerInterface['stopTests']>[0]) {
this._sendMessageNoReply('stopTests');
this._sendMessageNoReply('stopTests', params);
}
async closeGracefully(params: Parameters<TestServerInterface['closeGracefully']>[0]): ReturnType<TestServerInterface['closeGracefully']> {
await this._sendMessage('closeGracefully');
await this._sendMessage('closeGracefully', params);
}
close() {
try {
this._ws.close();
} catch {
}
}
}

View file

@ -21,7 +21,12 @@ import type { JsonEvent } from './teleReceiver';
export type ReportEntry = JsonEvent;
export interface TestServerInterface {
setSerializer(params: { serializer: string }): Promise<void>;
initialize(params: {
serializer?: string,
closeOnDisconnect?: boolean,
interceptStdio?: boolean,
watchTestDirs?: boolean,
}): Promise<void>;
ping(params: {}): Promise<void>;
@ -29,8 +34,6 @@ export interface TestServerInterface {
fileNames: string[];
}): Promise<void>;
watchTestDir(params: {}): Promise<void>;
open(params: { location: reporterTypes.Location }): Promise<void>;
resizeTerminal(params: { cols: number, rows: number }): Promise<void>;

View file

@ -295,20 +295,7 @@ export class TestTree {
}
collectTestIds(treeItem?: TreeItem): Set<string> {
const testIds = new Set<string>();
if (!treeItem)
return testIds;
const visit = (treeItem: TreeItem) => {
if (treeItem.kind === 'case')
treeItem.tests.map(t => t.id).forEach(id => testIds.add(id));
else if (treeItem.kind === 'test')
testIds.add(treeItem.id);
else
treeItem.children?.forEach(visit);
};
visit(treeItem);
return testIds;
return treeItem ? collectTestIds(treeItem) : new Set();
}
}
@ -349,4 +336,18 @@ export function sortAndPropagateStatus(treeItem: TreeItem) {
treeItem.status = 'passed';
}
export function collectTestIds(treeItem: TreeItem): Set<string> {
const testIds = new Set<string>();
const visit = (treeItem: TreeItem) => {
if (treeItem.kind === 'case')
treeItem.tests.map(t => t.id).forEach(id => testIds.add(id));
else if (treeItem.kind === 'test')
testIds.add(treeItem.id);
else
treeItem.children?.forEach(visit);
};
visit(treeItem);
return testIds;
}
export const statusEx = Symbol('statusEx');

View file

@ -169,6 +169,8 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
timeout: cliOverrides.timeout,
});
await stopProfiling('runner');
if (status === 'restarted')
return;
const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1);
gracefullyProcessExitDoNotHang(exitCode);
return;
@ -200,6 +202,8 @@ async function runTestServer(opts: { [key: string]: any }) {
const host = opts.host || 'localhost';
const port = opts.port ? +opts.port : 0;
const status = await testServer.runTestServer(opts.config, { host, port });
if (status === 'restarted')
return;
const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1);
gracefullyProcessExitDoNotHang(exitCode);
}

View file

@ -85,7 +85,6 @@ export function createTaskRunnerForWatch(config: FullConfigInternal, reporter: R
export function createTaskRunnerForTestServer(config: FullConfigInternal, reporter: ReporterV2): TaskRunner<TestRun> {
const taskRunner = new TaskRunner<TestRun>(reporter, 0);
addGlobalSetupTasks(taskRunner, config);
taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: true }));
addRunTasks(taskRunner, config);
return taskRunner;

View file

@ -39,16 +39,15 @@ import type { TraceViewerRedirectOptions, TraceViewerServerOptions } from 'playw
import type { TestRunnerPluginRegistration } from '../plugins';
import { serializeError } from '../util';
const originalStdoutWrite = process.stdout.write;
const originalStderrWrite = process.stderr.write;
class TestServer {
private _configFile: string | undefined;
private _dispatcher: TestServerDispatcher | undefined;
private _originalStdoutWrite: NodeJS.WriteStream['write'];
private _originalStderrWrite: NodeJS.WriteStream['write'];
constructor(configFile: string | undefined) {
this._configFile = configFile;
this._originalStdoutWrite = process.stdout.write;
this._originalStderrWrite = process.stderr.write;
}
async start(options: { host?: string, port?: number }): Promise<HttpServer> {
@ -57,28 +56,9 @@ class TestServer {
}
async stop() {
await this._dispatcher?._setInterceptStdio(false);
await this._dispatcher?.runGlobalTeardown();
}
wireStdIO() {
if (!process.env.PWTEST_DEBUG) {
process.stdout.write = (chunk: string | Buffer) => {
this._dispatcher?._dispatchEvent('stdio', chunkToPayload('stdout', chunk));
return true;
};
process.stderr.write = (chunk: string | Buffer) => {
this._dispatcher?._dispatchEvent('stdio', chunkToPayload('stderr', chunk));
return true;
};
}
}
unwireStdIO() {
if (!process.env.PWTEST_DEBUG) {
process.stdout.write = this._originalStdoutWrite;
process.stderr.write = this._originalStderrWrite;
}
}
}
class TestServerDispatcher implements TestServerInterface {
@ -92,13 +72,17 @@ class TestServerDispatcher implements TestServerInterface {
readonly _dispatchEvent: TestServerInterfaceEventEmitters['dispatchEvent'];
private _plugins: TestRunnerPluginRegistration[] | undefined;
private _serializer = require.resolve('./uiModeReporter');
private _watchTestDir = false;
private _watchTestDirs = false;
private _closeOnDisconnect = false;
constructor(configFile: string | undefined) {
this._configFile = configFile;
this.transport = {
dispatch: (method, params) => (this as any)[method](params),
onclose: () => gracefullyProcessExitDoNotHang(0),
onclose: () => {
if (this._closeOnDisconnect)
gracefullyProcessExitDoNotHang(0);
},
};
this._globalWatcher = new Watcher('deep', () => this._dispatchEvent('listChanged', {}));
this._testWatcher = new Watcher('flat', events => {
@ -109,10 +93,6 @@ class TestServerDispatcher implements TestServerInterface {
this._dispatchEvent = (method, params) => this.transport.sendEvent?.(method, params);
}
async setSerializer(params: { serializer: string; }): Promise<void> {
this._serializer = params.serializer;
}
private async _wireReporter(messageSink: (message: any) => void) {
return await createReporterForTestServer(this._serializer, messageSink);
}
@ -124,7 +104,16 @@ class TestServerDispatcher implements TestServerInterface {
return { reporter, report };
}
async ready() {}
async initialize(params: Parameters<TestServerInterface['initialize']>[0]): ReturnType<TestServerInterface['initialize']> {
if (params.serializer)
this._serializer = params.serializer;
if (params.closeOnDisconnect)
this._closeOnDisconnect = true;
if (params.interceptStdio)
await this._setInterceptStdio(true);
if (params.watchTestDirs)
this._watchTestDirs = true;
}
async ping() {}
@ -246,7 +235,7 @@ class TestServerDispatcher implements TestServerInterface {
projectOutputs.add(result.outDir);
}
if (this._watchTestDir)
if (this._watchTestDirs)
this._globalWatcher.update([...projectDirs], [...projectOutputs], false);
return { report, status };
}
@ -315,10 +304,6 @@ class TestServerDispatcher implements TestServerInterface {
return { status: await run };
}
async watchTestDir() {
this._watchTestDir = true;
}
async watch(params: { fileNames: string[]; }) {
const files = new Set<string>();
for (const fileName of params.fileNames) {
@ -341,6 +326,24 @@ class TestServerDispatcher implements TestServerInterface {
await this._testRun?.run;
}
async _setInterceptStdio(intercept: boolean) {
if (process.env.PWTEST_DEBUG)
return;
if (intercept) {
process.stdout.write = (chunk: string | Buffer) => {
this._dispatchEvent('stdio', chunkToPayload('stdout', chunk));
return true;
};
process.stderr.write = (chunk: string | Buffer) => {
this._dispatchEvent('stdio', chunkToPayload('stderr', chunk));
return true;
};
} else {
process.stdout.write = originalStdoutWrite;
process.stderr.write = originalStderrWrite;
}
}
async closeGracefully() {
gracefullyProcessExitDoNotHang(0);
}
@ -362,7 +365,7 @@ class TestServerDispatcher implements TestServerInterface {
}
}
export async function runUIMode(configFile: string | undefined, options: TraceViewerServerOptions & TraceViewerRedirectOptions): Promise<reporterTypes.FullResult['status']> {
export async function runUIMode(configFile: string | undefined, options: TraceViewerServerOptions & TraceViewerRedirectOptions): Promise<reporterTypes.FullResult['status'] | 'restarted'> {
return await innerRunTestServer(configFile, options, async (server: HttpServer, cancelPromise: ManualPromise<void>) => {
await installRootRedirect(server, [], { ...options, webApp: 'uiMode.html' });
if (options.host !== undefined || options.port !== undefined) {
@ -379,27 +382,26 @@ export async function runUIMode(configFile: string | undefined, options: TraceVi
});
}
export async function runTestServer(configFile: string | undefined, options: { host?: string, port?: number }): Promise<reporterTypes.FullResult['status']> {
export async function runTestServer(configFile: string | undefined, options: { host?: string, port?: number }): Promise<reporterTypes.FullResult['status'] | 'restarted'> {
return await innerRunTestServer(configFile, options, async server => {
// eslint-disable-next-line no-console
console.log('Listening on ' + server.urlPrefix().replace('http:', 'ws:') + '/' + server.wsGuid());
});
}
async function innerRunTestServer(configFile: string | undefined, options: { host?: string, port?: number }, openUI: (server: HttpServer, cancelPromise: ManualPromise<void>) => Promise<void>): Promise<reporterTypes.FullResult['status']> {
async function innerRunTestServer(configFile: string | undefined, options: { host?: string, port?: number }, openUI: (server: HttpServer, cancelPromise: ManualPromise<void>) => Promise<void>): Promise<reporterTypes.FullResult['status'] | 'restarted'> {
if (restartWithExperimentalTsEsm(undefined, true))
return 'passed';
return 'restarted';
const testServer = new TestServer(configFile);
const cancelPromise = new ManualPromise<void>();
const sigintWatcher = new SigIntWatcher();
process.stdin.on('close', () => gracefullyProcessExitDoNotHang(0));
void sigintWatcher.promise().then(() => cancelPromise.resolve());
try {
const server = await testServer.start(options);
await openUI(server, cancelPromise);
testServer.wireStdIO();
await cancelPromise;
} finally {
testServer.unwireStdIO();
await testServer.stop();
sigintWatcher.disarm();
}

View file

@ -29,32 +29,32 @@
font-weight: 600;
}
.light-mode .tag-color-0 {
.tag-color-0 {
background-color: #ddf4ff;
color: #0550ae;
border: 1px solid #218bff;
}
.light-mode .tag-color-1 {
.tag-color-1 {
background-color: #fff8c5;
color: #7d4e00;
border: 1px solid #bf8700;
}
.light-mode .tag-color-2 {
.tag-color-2 {
background-color: #fbefff;
color: #6e40c9;
border: 1px solid #a475f9;
}
.light-mode .tag-color-3 {
.tag-color-3 {
background-color: #ffeff7;
color: #99286e;
border: 1px solid #e85aad;
}
.light-mode .tag-color-4 {
.tag-color-4 {
background-color: #FFF0EB;
color: #9E2F1C;
border: 1px solid #EA6045;
}
.light-mode .tag-color-5 {
.tag-color-5 {
background-color: #fff1e5;
color: #9b4215;
border: 1px solid #e16f24;

View file

@ -123,9 +123,10 @@ export class TeleSuiteUpdater {
}
processListReport(report: any[]) {
this._receiver.reset();
this._receiver.isListing = true;
for (const message of report)
this._receiver.dispatch(message);
this._receiver.isListing = false;
}
processTestReportEvent(message: any) {

View file

@ -172,7 +172,10 @@ export const UIModeView: React.FC<{}> = ({
setIsLoading(true);
setWatchedTreeIds({ value: new Set() });
(async () => {
await testServerConnection.watchTestDir({});
await testServerConnection.initialize({
interceptStdio: true,
watchTestDirs: true
});
const { status } = await testServerConnection.runGlobalSetup({});
if (status !== 'passed')
return;

View file

@ -93,6 +93,7 @@ export const WorkbenchLoader: React.FunctionComponent<{
setDragOver(false);
setProcessingErrorMessage(null);
});
testServerConnection.initialize({}).catch(() => {});
} else if (!newTraceURLs.some(url => url.startsWith('blob:'))) {
// Don't re-use blob file URLs on page load (results in Fetch error)
setTraceURLs(newTraceURLs);

View file

@ -15,16 +15,13 @@
*/
import { browserTest, expect } from '../config/browserTest';
import type { BrowserContext, BrowserContextOptions } from '@playwright/test';
import type { BrowserContext } from '@playwright/test';
const test = browserTest.extend<{ reusedContext: (options?: BrowserContextOptions) => Promise<BrowserContext> }>({
const test = browserTest.extend<{ reusedContext: () => Promise<BrowserContext> }>({
reusedContext: async ({ browserType, browser }, use) => {
await use(async (options: BrowserContextOptions = {}) => {
await use(async () => {
const defaultContextOptions = (browserType as any)._defaultContextOptions;
const context = await (browser as any)._newContextForReuse({
...defaultContextOptions,
...options,
});
const context = await (browser as any)._newContextForReuse(defaultContextOptions);
return context;
});
},
@ -238,33 +235,6 @@ test('should reset mouse position', async ({ reusedContext, browserName, platfor
await expect(page.locator('#two')).toHaveCSS('background-color', 'rgb(0, 0, 255)');
});
test('should reset Origin Private File System', async ({ reusedContext, httpsServer, browserName }) => {
test.skip(browserName === 'webkit', 'getDirectory is not supported in ephemeral context in WebKit https://github.com/microsoft/playwright/issues/18235#issuecomment-1289792576');
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/29901' });
let context = await reusedContext({ ignoreHTTPSErrors: true });
let page = await context.newPage();
await page.goto(httpsServer.EMPTY_PAGE);
await page.evaluate(async () => {
const root = await navigator.storage.getDirectory();
await root.getDirectoryHandle('someDirectoryName', { create: true });
await root.getFileHandle('foo.txt', { create: true });
});
context = await reusedContext({ ignoreHTTPSErrors: true });
page = await context.newPage();
await page.goto(httpsServer.EMPTY_PAGE);
const { directoryExits, fileExits } = await page.evaluate(async () => {
const root = await navigator.storage.getDirectory();
let directoryExits = true, fileExits = true;
await root.getDirectoryHandle('someDirectoryName').catch(() => { directoryExits = false; });
await root.getFileHandle('foo.txt').catch(() => { fileExits = false; });
return { directoryExits, fileExits };
});
expect(directoryExits).toBe(false);
expect(fileExits).toBe(false);
});
test('should reset tracing', async ({ reusedContext, trace }, testInfo) => {
test.skip(trace === 'on');

View file

@ -129,6 +129,36 @@ test('should pick new / deleted tests', async ({ runUITest, writeFiles, deleteFi
`);
});
test('should not loose run information after execution if test wrote into testDir', async ({ runUITest, writeFiles, deleteFile }) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30300' });
const { page } = await runUITest({
'a.test.ts': `
import fs from 'fs';
import path from 'path';
import { test, expect } from '@playwright/test';
test('passes', () => {
fs.writeFileSync(path.join(test.info().project.testDir, 'something.txt'), 'hi');
});
`,
});
await expect.poll(dumpTestTree(page)).toBe(`
a.test.ts
passes
`);
await page.getByTitle('passes').click();
await page.getByTitle('Run all').click();
await page.waitForTimeout(5_000);
await expect(page.getByText('Did not run')).toBeHidden();
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
await expect(
listItem,
'action list'
).toHaveText([
/Before Hooks[\d.]+m?s/,
/After Hooks[\d.]+m?s/,
]);
});
test('should pick new / deleted nested tests', async ({ runUITest, writeFiles, deleteFile }) => {
const { page } = await runUITest(basicTestTree);
await expect.poll(dumpTestTree(page)).toContain(`