Compare commits

...

10 commits

Author SHA1 Message Date
Andrey Lushnikov 1f983acac0
chore: mark 1.36.2 (#24379) 2023-07-24 10:43:17 -07:00
Andrey Lushnikov 40e2096fbc cherry-pick(#24371): fix: properly handle character sets in globs
https://github.com/microsoft/playwright/issues/24316
2023-07-24 21:32:02 +04:00
Andrey Lushnikov 4417b78552
chore: mark 1.36.1 (#24230) 2023-07-14 07:39:42 -07:00
Playwright Service cf5900f142
cherry-pick(#24213): Revert "fix: do not collide with other tests when test names have special chars (#23414)" (#24217)
This PR cherry-picks the following commits:

- 57cca1d96e

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-07-14 06:33:48 -07:00
Dmitry Gozman 74ec8c243a
cherry-pick(#24145): fix(snapshots): match resources by method (#24147)
Fixes #24144.

Previously, we only matched by url, which confuses GET and HEAD requests
where the latter is usually zero-sized.

Also make sure that resources are sorted by their monotonicTime, since
that's not always the case in the trace file, where they are sorted by
the "response body retrieved" time.
2023-07-10 22:10:57 -07:00
Andrey Lushnikov 1e8e8b4b02
chore: mark 1.36.0 (#24129) 2023-07-10 12:01:37 -07:00
Andrey Lushnikov 6ee70e2ce3 cherry-pick(#24135): docs: add release notes for 1.36 2023-07-10 22:59:23 +04:00
Pavel Feldman e9e6cf551f cherry-pick(#24106): fix(trace): do not allow after w/o before
Fixes https://github.com/microsoft/playwright/issues/24087,
https://github.com/microsoft/playwright/issues/23802
2023-07-10 21:29:24 +04:00
Andrey Lushnikov b7dcc2bb16 cherry-pick(#24127): fix: do not create empty directories for successful snapshot tests
Fixes https://github.com/microsoft/playwright/issues/15600
2023-07-10 21:25:16 +04:00
Andrey Lushnikov 52f594e0eb cherry-pick(#24128): chore: update WebKit browser version to 17.0 2023-07-10 19:11:30 +04:00
44 changed files with 358 additions and 254 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-115.0.5790.75-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-115.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-16.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-115.0.5790.75-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-115.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.0-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop -->
## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)
@ -9,7 +9,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->115.0.5790.75<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->16.4<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->17.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->115.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all browsers on all platforms. Check out [system requirements](https://playwright.dev/docs/intro#system-requirements) for details.

View file

@ -4,6 +4,21 @@ title: "Release notes"
toc_max_heading_level: 2
---
## Version 1.36
🏝️ Summer maintenance release.
### Browser Versions
* Chromium 115.0.5790.75
* Mozilla Firefox 115.0
* WebKit 17.0
This version was also tested against the following stable channels:
* Google Chrome 114
* Microsoft Edge 114
## Version 1.35
### Highlights

View file

@ -12,6 +12,21 @@ toc_max_heading_level: 2
import LiteYouTube from '@site/src/components/LiteYouTube';
## Version 1.36
🏝️ Summer maintenance release.
### Browser Versions
* Chromium 115.0.5790.75
* Mozilla Firefox 115.0
* WebKit 17.0
This version was also tested against the following stable channels:
* Google Chrome 114
* Microsoft Edge 114
## Version 1.35
### Highlights

View file

@ -6,6 +6,21 @@ toc_max_heading_level: 2
import LiteYouTube from '@site/src/components/LiteYouTube';
## Version 1.36
🏝️ Summer maintenance release.
### Browser Versions
* Chromium 115.0.5790.75
* Mozilla Firefox 115.0
* WebKit 17.0
This version was also tested against the following stable channels:
* Google Chrome 114
* Microsoft Edge 114
## Version 1.35
<LiteYouTube

View file

@ -4,6 +4,21 @@ title: "Release notes"
toc_max_heading_level: 2
---
## Version 1.36
🏝️ Summer maintenance release.
### Browser Versions
* Chromium 115.0.5790.75
* Mozilla Firefox 115.0
* WebKit 17.0
This version was also tested against the following stable channels:
* Google Chrome 114
* Microsoft Edge 114
## Version 1.35
### Highlights

82
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "playwright-internal",
"version": "1.36.0-next",
"version": "1.36.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "playwright-internal",
"version": "1.36.0-next",
"version": "1.36.2",
"license": "Apache-2.0",
"workspaces": [
"packages/*"
@ -6191,11 +6191,11 @@
}
},
"packages/playwright": {
"version": "1.36.0-next",
"version": "1.36.2",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
},
"bin": {
"playwright": "cli.js"
@ -6205,11 +6205,11 @@
}
},
"packages/playwright-chromium": {
"version": "1.36.0-next",
"version": "1.36.2",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
},
"bin": {
"playwright": "cli.js"
@ -6219,7 +6219,7 @@
}
},
"packages/playwright-core": {
"version": "1.36.0-next",
"version": "1.36.2",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
@ -6230,11 +6230,11 @@
},
"packages/playwright-ct-core": {
"name": "@playwright/experimental-ct-core",
"version": "1.36.0-next",
"version": "1.36.2",
"license": "Apache-2.0",
"dependencies": {
"@playwright/test": "1.36.0-next",
"playwright-core": "1.36.0-next",
"@playwright/test": "1.36.2",
"playwright-core": "1.36.2",
"vite": "^4.3.9"
},
"bin": {
@ -6246,10 +6246,10 @@
},
"packages/playwright-ct-react": {
"name": "@playwright/experimental-ct-react",
"version": "1.36.0-next",
"version": "1.36.2",
"license": "Apache-2.0",
"dependencies": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@vitejs/plugin-react": "^4.0.0"
},
"bin": {
@ -6278,10 +6278,10 @@
},
"packages/playwright-ct-react17": {
"name": "@playwright/experimental-ct-react17",
"version": "1.36.0-next",
"version": "1.36.2",
"license": "Apache-2.0",
"dependencies": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@vitejs/plugin-react": "^4.0.0"
},
"bin": {
@ -6310,10 +6310,10 @@
},
"packages/playwright-ct-solid": {
"name": "@playwright/experimental-ct-solid",
"version": "1.36.0-next",
"version": "1.36.2",
"license": "Apache-2.0",
"dependencies": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"vite-plugin-solid": "^2.7.0"
},
"bin": {
@ -6328,10 +6328,10 @@
},
"packages/playwright-ct-svelte": {
"name": "@playwright/experimental-ct-svelte",
"version": "1.36.0-next",
"version": "1.36.2",
"license": "Apache-2.0",
"dependencies": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@sveltejs/vite-plugin-svelte": "^2.1.1"
},
"bin": {
@ -6346,10 +6346,10 @@
},
"packages/playwright-ct-vue": {
"name": "@playwright/experimental-ct-vue",
"version": "1.36.0-next",
"version": "1.36.2",
"license": "Apache-2.0",
"dependencies": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@vitejs/plugin-vue": "^4.2.1"
},
"bin": {
@ -6397,10 +6397,10 @@
},
"packages/playwright-ct-vue2": {
"name": "@playwright/experimental-ct-vue2",
"version": "1.36.0-next",
"version": "1.36.2",
"license": "Apache-2.0",
"dependencies": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@vitejs/plugin-vue2": "^2.2.0"
},
"bin": {
@ -6414,11 +6414,11 @@
}
},
"packages/playwright-firefox": {
"version": "1.36.0-next",
"version": "1.36.2",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
},
"bin": {
"playwright": "cli.js"
@ -6429,11 +6429,11 @@
},
"packages/playwright-test": {
"name": "@playwright/test",
"version": "1.36.0-next",
"version": "1.36.2",
"license": "Apache-2.0",
"dependencies": {
"@types/node": "*",
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
},
"bin": {
"playwright": "cli.js"
@ -6446,11 +6446,11 @@
}
},
"packages/playwright-webkit": {
"version": "1.36.0-next",
"version": "1.36.2",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
},
"bin": {
"playwright": "cli.js"
@ -7260,15 +7260,15 @@
"@playwright/experimental-ct-core": {
"version": "file:packages/playwright-ct-core",
"requires": {
"@playwright/test": "1.36.0-next",
"playwright-core": "1.36.0-next",
"@playwright/test": "1.36.2",
"playwright-core": "1.36.2",
"vite": "^4.3.9"
}
},
"@playwright/experimental-ct-react": {
"version": "file:packages/playwright-ct-react",
"requires": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@vitejs/plugin-react": "^4.0.0"
},
"dependencies": {
@ -7288,7 +7288,7 @@
"@playwright/experimental-ct-react17": {
"version": "file:packages/playwright-ct-react17",
"requires": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@vitejs/plugin-react": "^4.0.0"
},
"dependencies": {
@ -7308,7 +7308,7 @@
"@playwright/experimental-ct-solid": {
"version": "file:packages/playwright-ct-solid",
"requires": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"solid-js": "^1.7.0",
"vite-plugin-solid": "^2.7.0"
}
@ -7316,7 +7316,7 @@
"@playwright/experimental-ct-svelte": {
"version": "file:packages/playwright-ct-svelte",
"requires": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@sveltejs/vite-plugin-svelte": "^2.1.1",
"svelte": "^3.55.1"
}
@ -7324,7 +7324,7 @@
"@playwright/experimental-ct-vue": {
"version": "file:packages/playwright-ct-vue",
"requires": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@vitejs/plugin-vue": "^4.2.1"
},
"dependencies": {
@ -7358,7 +7358,7 @@
"@playwright/experimental-ct-vue2": {
"version": "file:packages/playwright-ct-vue2",
"requires": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@vitejs/plugin-vue2": "^2.2.0",
"vue": "^2.7.14"
}
@ -7368,7 +7368,7 @@
"requires": {
"@types/node": "*",
"fsevents": "2.3.2",
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
}
},
"@sindresorhus/is": {
@ -9656,13 +9656,13 @@
"playwright": {
"version": "file:packages/playwright",
"requires": {
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
}
},
"playwright-chromium": {
"version": "file:packages/playwright-chromium",
"requires": {
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
}
},
"playwright-core": {
@ -9671,13 +9671,13 @@
"playwright-firefox": {
"version": "file:packages/playwright-firefox",
"requires": {
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
}
},
"playwright-webkit": {
"version": "file:packages/playwright-webkit",
"requires": {
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
}
},
"postcss": {

View file

@ -1,7 +1,7 @@
{
"name": "playwright-internal",
"private": true,
"version": "1.36.0-next",
"version": "1.36.2",
"description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",

View file

@ -1,6 +1,6 @@
{
"name": "playwright-chromium",
"version": "1.36.0-next",
"version": "1.36.2",
"description": "A high-level API to automate Chromium",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -27,6 +27,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
}
}

View file

@ -42,7 +42,7 @@
"mac11-arm64": "1816",
"ubuntu18.04": "1728"
},
"browserVersion": "16.4"
"browserVersion": "17.0"
},
{
"name": "ffmpeg",

View file

@ -1,6 +1,6 @@
{
"name": "playwright-core",
"version": "1.36.0-next",
"version": "1.36.2",
"description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",

View file

@ -1,6 +1,6 @@
{
"Blackberry PlayBook": {
"userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/16.4 Safari/536.2+",
"userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/17.0 Safari/536.2+",
"viewport": {
"width": 600,
"height": 1024
@ -11,7 +11,7 @@
"defaultBrowserType": "webkit"
},
"Blackberry PlayBook landscape": {
"userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/16.4 Safari/536.2+",
"userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/17.0 Safari/536.2+",
"viewport": {
"width": 1024,
"height": 600
@ -22,7 +22,7 @@
"defaultBrowserType": "webkit"
},
"BlackBerry Z30": {
"userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/16.4 Mobile Safari/537.10+",
"userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/17.0 Mobile Safari/537.10+",
"viewport": {
"width": 360,
"height": 640
@ -33,7 +33,7 @@
"defaultBrowserType": "webkit"
},
"BlackBerry Z30 landscape": {
"userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/16.4 Mobile Safari/537.10+",
"userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/17.0 Mobile Safari/537.10+",
"viewport": {
"width": 640,
"height": 360
@ -44,7 +44,7 @@
"defaultBrowserType": "webkit"
},
"Galaxy Note 3": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/16.4 Mobile Safari/534.30",
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/17.0 Mobile Safari/534.30",
"viewport": {
"width": 360,
"height": 640
@ -55,7 +55,7 @@
"defaultBrowserType": "webkit"
},
"Galaxy Note 3 landscape": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/16.4 Mobile Safari/534.30",
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/17.0 Mobile Safari/534.30",
"viewport": {
"width": 640,
"height": 360
@ -66,7 +66,7 @@
"defaultBrowserType": "webkit"
},
"Galaxy Note II": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/16.4 Mobile Safari/534.30",
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/17.0 Mobile Safari/534.30",
"viewport": {
"width": 360,
"height": 640
@ -77,7 +77,7 @@
"defaultBrowserType": "webkit"
},
"Galaxy Note II landscape": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/16.4 Mobile Safari/534.30",
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/17.0 Mobile Safari/534.30",
"viewport": {
"width": 640,
"height": 360
@ -88,7 +88,7 @@
"defaultBrowserType": "webkit"
},
"Galaxy S III": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/16.4 Mobile Safari/534.30",
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/17.0 Mobile Safari/534.30",
"viewport": {
"width": 360,
"height": 640
@ -99,7 +99,7 @@
"defaultBrowserType": "webkit"
},
"Galaxy S III landscape": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/16.4 Mobile Safari/534.30",
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/17.0 Mobile Safari/534.30",
"viewport": {
"width": 640,
"height": 360
@ -198,7 +198,7 @@
"defaultBrowserType": "chromium"
},
"iPad (gen 6)": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"viewport": {
"width": 768,
"height": 1024
@ -209,7 +209,7 @@
"defaultBrowserType": "webkit"
},
"iPad (gen 6) landscape": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"viewport": {
"width": 1024,
"height": 768
@ -220,7 +220,7 @@
"defaultBrowserType": "webkit"
},
"iPad (gen 7)": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"viewport": {
"width": 810,
"height": 1080
@ -231,7 +231,7 @@
"defaultBrowserType": "webkit"
},
"iPad (gen 7) landscape": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"viewport": {
"width": 1080,
"height": 810
@ -242,7 +242,7 @@
"defaultBrowserType": "webkit"
},
"iPad Mini": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"viewport": {
"width": 768,
"height": 1024
@ -253,7 +253,7 @@
"defaultBrowserType": "webkit"
},
"iPad Mini landscape": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"viewport": {
"width": 1024,
"height": 768
@ -264,7 +264,7 @@
"defaultBrowserType": "webkit"
},
"iPad Pro 11": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"viewport": {
"width": 834,
"height": 1194
@ -275,7 +275,7 @@
"defaultBrowserType": "webkit"
},
"iPad Pro 11 landscape": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"viewport": {
"width": 1194,
"height": 834
@ -286,7 +286,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 6": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 375,
"height": 667
@ -297,7 +297,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 6 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 667,
"height": 375
@ -308,7 +308,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 6 Plus": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 414,
"height": 736
@ -319,7 +319,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 6 Plus landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 736,
"height": 414
@ -330,7 +330,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 7": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 375,
"height": 667
@ -341,7 +341,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 7 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 667,
"height": 375
@ -352,7 +352,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 7 Plus": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 414,
"height": 736
@ -363,7 +363,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 7 Plus landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 736,
"height": 414
@ -374,7 +374,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 8": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 375,
"height": 667
@ -385,7 +385,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 8 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 667,
"height": 375
@ -396,7 +396,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 8 Plus": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 414,
"height": 736
@ -407,7 +407,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 8 Plus landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 736,
"height": 414
@ -418,7 +418,7 @@
"defaultBrowserType": "webkit"
},
"iPhone SE": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/16.4 Mobile/14E304 Safari/602.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/17.0 Mobile/14E304 Safari/602.1",
"viewport": {
"width": 320,
"height": 568
@ -429,7 +429,7 @@
"defaultBrowserType": "webkit"
},
"iPhone SE landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/16.4 Mobile/14E304 Safari/602.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/17.0 Mobile/14E304 Safari/602.1",
"viewport": {
"width": 568,
"height": 320
@ -440,7 +440,7 @@
"defaultBrowserType": "webkit"
},
"iPhone X": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 375,
"height": 812
@ -451,7 +451,7 @@
"defaultBrowserType": "webkit"
},
"iPhone X landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/16.4 Mobile/15A372 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.0 Mobile/15A372 Safari/604.1",
"viewport": {
"width": 812,
"height": 375
@ -462,7 +462,7 @@
"defaultBrowserType": "webkit"
},
"iPhone XR": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"viewport": {
"width": 414,
"height": 896
@ -473,7 +473,7 @@
"defaultBrowserType": "webkit"
},
"iPhone XR landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"viewport": {
"width": 896,
"height": 414
@ -484,7 +484,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 11": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 414,
"height": 896
@ -499,7 +499,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 11 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 414,
"height": 896
@ -514,7 +514,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 11 Pro": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 375,
"height": 812
@ -529,7 +529,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 11 Pro landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 375,
"height": 812
@ -544,7 +544,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 11 Pro Max": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 414,
"height": 896
@ -559,7 +559,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 11 Pro Max landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 414,
"height": 896
@ -574,7 +574,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 12": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 390,
"height": 844
@ -589,7 +589,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 12 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 390,
"height": 844
@ -604,7 +604,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 12 Pro": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 390,
"height": 844
@ -619,7 +619,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 12 Pro landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 390,
"height": 844
@ -634,7 +634,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 12 Pro Max": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 428,
"height": 926
@ -649,7 +649,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 12 Pro Max landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 428,
"height": 926
@ -664,7 +664,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 12 Mini": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 375,
"height": 812
@ -679,7 +679,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 12 Mini landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 375,
"height": 812
@ -694,7 +694,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 13": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 390,
"height": 844
@ -709,7 +709,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 13 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 390,
"height": 844
@ -724,7 +724,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 13 Pro": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 390,
"height": 844
@ -739,7 +739,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 13 Pro landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 390,
"height": 844
@ -754,7 +754,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 13 Pro Max": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 428,
"height": 926
@ -769,7 +769,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 13 Pro Max landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 428,
"height": 926
@ -784,7 +784,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 13 Mini": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 375,
"height": 812
@ -799,7 +799,7 @@
"defaultBrowserType": "webkit"
},
"iPhone 13 Mini landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"screen": {
"width": 375,
"height": 812
@ -1315,7 +1315,7 @@
"defaultBrowserType": "firefox"
},
"Desktop Safari": {
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
"screen": {
"width": 1792,
"height": 1120

View file

@ -437,6 +437,12 @@ export class HarTracer {
const pageEntry = this._createPageEntryIfNeeded(page);
const request = response.request();
// Prefer "response received" time over "request sent" time
// for the purpose of matching requests that were used in a particular snapshot.
// Note that both snapshot time and request time are taken here in the Node process.
if (this._options.includeTraceInfo)
harEntry._monotonicTime = monotonicTime();
harEntry.response = {
status: response.status(),
statusText: response.statusText(),

View file

@ -62,6 +62,7 @@ type RecordingState = {
networkSha1s: Set<string>,
traceSha1s: Set<string>,
recording: boolean;
callIds: Set<string>;
};
const kScreencastOptions = { width: 800, height: 600, quality: 90 };
@ -146,7 +147,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
chunkOrdinal: 0,
traceSha1s: new Set(),
networkSha1s: new Set(),
recording: false
recording: false,
callIds: new Set(),
};
const state = this._state;
this._writeChain = fs.promises.mkdir(state.resourcesDir, { recursive: true }).then(() => fs.promises.writeFile(state.networkFile.file, ''));
@ -171,6 +173,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
buffer: [],
};
state.recording = true;
state.callIds.clear();
if (options.name && options.name !== this._state.traceName)
this._changeTraceName(this._state, options.name);
@ -352,11 +355,14 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
return Promise.resolve();
sdkObject.attribution.page?.temporarlyDisableTracingScreencastThrottling();
event.beforeSnapshot = `before@${metadata.id}`;
this._state?.callIds.add(metadata.id);
this._appendTraceEvent(event);
return this._captureSnapshot(event.beforeSnapshot, sdkObject, metadata);
}
onBeforeInputAction(sdkObject: SdkObject, metadata: CallMetadata, element: ElementHandle) {
if (!this._state?.callIds.has(metadata.id))
return Promise.resolve();
// IMPORTANT: no awaits before this._appendTraceEvent in this method.
const event = createInputActionTraceEvent(metadata);
if (!event)
@ -368,9 +374,12 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
}
async onAfterCall(sdkObject: SdkObject, metadata: CallMetadata) {
if (!this._state?.callIds.has(metadata.id))
return;
this._state?.callIds.delete(metadata.id);
const event = createAfterActionTraceEvent(metadata);
if (!event)
return Promise.resolve();
return;
sdkObject.attribution.page?.temporarlyDisableTracingScreencastThrottling();
event.afterSnapshot = `after@${metadata.id}`;
this._appendTraceEvent(event);

View file

@ -33,8 +33,8 @@ import { WKPage } from './wkPage';
import { kBrowserClosedError } from '../../common/errors';
import type { SdkObject } from '../instrumentation';
const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15';
const BROWSER_VERSION = '16.4';
const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15';
const BROWSER_VERSION = '17.0';
export class WKBrowser extends Browser {
private readonly _connection: WKConnection;

View file

@ -51,6 +51,12 @@ export function globToRegex(glob: string): RegExp {
case '?':
tokens.push('.');
break;
case '[':
tokens.push('[');
break;
case ']':
tokens.push(']');
break;
case '{':
inGroup = true;
tokens.push('(');

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-core",
"version": "1.36.0-next",
"version": "1.36.2",
"description": "Playwright Component Testing Helpers",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -21,9 +21,9 @@
"./lib/vitePlugin": "./lib/vitePlugin.js"
},
"dependencies": {
"playwright-core": "1.36.0-next",
"playwright-core": "1.36.2",
"vite": "^4.3.9",
"@playwright/test": "1.36.0-next"
"@playwright/test": "1.36.2"
},
"bin": {
"playwright": "./cli.js"

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-react",
"version": "1.36.0-next",
"version": "1.36.2",
"description": "Playwright Component Testing for React",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -26,7 +26,7 @@
}
},
"dependencies": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@vitejs/plugin-react": "^4.0.0"
},
"bin": {

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-react17",
"version": "1.36.0-next",
"version": "1.36.2",
"description": "Playwright Component Testing for React",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -26,7 +26,7 @@
}
},
"dependencies": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@vitejs/plugin-react": "^4.0.0"
},
"bin": {

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-solid",
"version": "1.36.0-next",
"version": "1.36.2",
"description": "Playwright Component Testing for Solid",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -26,7 +26,7 @@
}
},
"dependencies": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"vite-plugin-solid": "^2.7.0"
},
"devDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-svelte",
"version": "1.36.0-next",
"version": "1.36.2",
"description": "Playwright Component Testing for Svelte",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -26,7 +26,7 @@
}
},
"dependencies": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@sveltejs/vite-plugin-svelte": "^2.1.1"
},
"devDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-vue",
"version": "1.36.0-next",
"version": "1.36.2",
"description": "Playwright Component Testing for Vue",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -26,7 +26,7 @@
}
},
"dependencies": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@vitejs/plugin-vue": "^4.2.1"
},
"bin": {

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-vue2",
"version": "1.36.0-next",
"version": "1.36.2",
"description": "Playwright Component Testing for Vue2",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -26,7 +26,7 @@
}
},
"dependencies": {
"@playwright/experimental-ct-core": "1.36.0-next",
"@playwright/experimental-ct-core": "1.36.2",
"@vitejs/plugin-vue2": "^2.2.0"
},
"devDependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "playwright-firefox",
"version": "1.36.0-next",
"version": "1.36.2",
"description": "A high-level API to automate Firefox",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -27,6 +27,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/test",
"version": "1.36.0-next",
"version": "1.36.2",
"description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -41,7 +41,7 @@
"license": "Apache-2.0",
"dependencies": {
"@types/node": "*",
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
},
"optionalDependencies": {
"fsevents": "2.3.2"

View file

@ -22,7 +22,7 @@ import type { ImageComparatorOptions, Comparator } from 'playwright-core/lib/uti
import { getComparator } from 'playwright-core/lib/utils';
import type { PageScreenshotOptions } from 'playwright-core/types/types';
import {
serializeError, sanitizeForFilePath,
addSuffixToFilePath, serializeError, sanitizeForFilePath,
trimLongString, callLogText,
expectTypes } from '../util';
import { colors } from 'playwright-core/lib/utilsBundle';
@ -124,8 +124,8 @@ class SnapshotHelper<T extends ImageComparatorOptions> {
const inputPathSegments = Array.isArray(name) ? name : [addSuffixToFilePath(name, '', undefined, true)];
const outputPathSegments = Array.isArray(name) ? name : [addSuffixToFilePath(name, actualModifier, undefined, true)];
this.snapshotPath = snapshotPathResolver(...inputPathSegments);
const inputFile = testInfo.outputPath(...inputPathSegments);
const outputFile = testInfo.outputPath(...outputPathSegments);
const inputFile = testInfo._getOutputPath(...inputPathSegments);
const outputFile = testInfo._getOutputPath(...outputPathSegments);
this.expectedPath = addSuffixToFilePath(inputFile, '-expected');
this.previousPath = addSuffixToFilePath(outputFile, '-previous');
this.actualPath = addSuffixToFilePath(outputFile, '-actual');
@ -440,14 +440,3 @@ function determineFileExtension(file: string | Buffer): string {
function compareMagicBytes(file: Buffer, magicBytes: number[]): boolean {
return Buffer.compare(Buffer.from(magicBytes), file.slice(0, magicBytes.length)) === 0;
}
function addSuffixToFilePath(filePath: string, suffix: string, customExtension?: string, sanitize = false): string {
const dirname = path.dirname(filePath);
const ext = path.extname(filePath);
const name = path.basename(filePath, ext);
const base = path.join(dirname, name);
const sanitizeForSnapshotFilePath = (s: string) => {
return s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-');
};
return (sanitize ? sanitizeForSnapshotFilePath(base) : base) + suffix + (customExtension || ext);
}

View file

@ -195,18 +195,8 @@ export function expectTypes(receiver: any, types: string[], matcherName: string)
}
}
export function sanitizeForFilePath(input: string) {
let nonTrivialSubstitute = false;
let sanitized = input.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F\x2A\-\*]+/g, substring => {
if (substring !== ' ')
nonTrivialSubstitute = true;
return '-';
});
if (!nonTrivialSubstitute)
return sanitized;
// If we sanitized the beginning or end, remove it for cosmetic reasons.
sanitized = sanitized.replace(/^-/, '').replace(/-$/, '');
return sanitized + '-' + calculateSha1(input).substring(0, 6);
export function sanitizeForFilePath(s: string) {
return s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-');
}
export function trimLongString(s: string, length = 100) {
@ -219,6 +209,14 @@ export function trimLongString(s: string, length = 100) {
return s.substring(0, start) + middle + s.slice(-end);
}
export function addSuffixToFilePath(filePath: string, suffix: string, customExtension?: string, sanitize = false): string {
const dirname = path.dirname(filePath);
const ext = path.extname(filePath);
const name = path.basename(filePath, ext);
const base = path.join(dirname, name);
return (sanitize ? sanitizeForFilePath(base) : base) + suffix + (customExtension || ext);
}
/**
* Returns absolute path contained within parent directory.
*/

View file

@ -382,7 +382,12 @@ export class TestInfoImpl implements TestInfo {
}
outputPath(...pathSegments: string[]){
const outputPath = this._getOutputPath(...pathSegments);
fs.mkdirSync(this.outputDir, { recursive: true });
return outputPath;
}
_getOutputPath(...pathSegments: string[]){
const joinedPath = path.join(...pathSegments);
const outputPath = getContainedPath(this.outputDir, joinedPath);
if (outputPath)

View file

@ -1,6 +1,6 @@
{
"name": "playwright-webkit",
"version": "1.36.0-next",
"version": "1.36.2",
"description": "A high-level API to automate WebKit",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -27,6 +27,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "playwright",
"version": "1.36.0-next",
"version": "1.36.2",
"description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -28,6 +28,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.36.0-next"
"playwright-core": "1.36.2"
}
}

View file

@ -112,17 +112,19 @@ export class SnapshotRenderer {
return { html, pageId: snapshot.pageId, frameId: snapshot.frameId, index: this._index };
}
resourceByUrl(url: string): ResourceSnapshot | undefined {
resourceByUrl(url: string, method: string): ResourceSnapshot | undefined {
const snapshot = this._snapshot;
let result: ResourceSnapshot | undefined;
// First try locating exact resource belonging to this frame.
for (const resource of this._resources) {
// Only use resources that received response before the snapshot.
// Note that both snapshot time and request time are taken in the same Node process.
if (typeof resource._monotonicTime === 'number' && resource._monotonicTime >= snapshot.timestamp)
break;
if (resource._frameref !== snapshot.frameId)
continue;
if (resource.request.url === url) {
if (resource.request.url === url && resource.request.method === method) {
// Pick the last resource with matching url - most likely it was used
// at the time of snapshot, not the earlier aborted resource with the same url.
result = resource;
@ -132,9 +134,11 @@ export class SnapshotRenderer {
if (!result) {
// Then fall back to resource with this URL to account for memory cache.
for (const resource of this._resources) {
// Only use resources that received response before the snapshot.
// Note that both snapshot time and request time are taken in the same Node process.
if (typeof resource._monotonicTime === 'number' && resource._monotonicTime >= snapshot.timestamp)
break;
if (resource.request.url === url) {
if (resource.request.url === url && resource.request.method === method) {
// Pick the last resource with matching url - most likely it was used
// at the time of snapshot, not the earlier aborted resource with the same url.
result = resource;
@ -142,7 +146,7 @@ export class SnapshotRenderer {
}
}
if (result) {
if (result && method.toUpperCase() === 'GET') {
// Patch override if necessary.
for (const o of snapshot.resourceOverrides) {
if (url === o.url && o.sha1) {

View file

@ -65,11 +65,11 @@ export class SnapshotServer {
});
}
async serveResource(requestUrlAlternatives: string[], snapshotUrl: string): Promise<Response> {
async serveResource(requestUrlAlternatives: string[], method: string, snapshotUrl: string): Promise<Response> {
let resource: ResourceSnapshot | undefined;
const snapshot = this._snapshotIds.get(snapshotUrl)!;
for (const requestUrl of requestUrlAlternatives) {
resource = snapshot?.resourceByUrl(removeHash(requestUrl));
resource = snapshot?.resourceByUrl(removeHash(requestUrl), method);
if (resource)
break;
}

View file

@ -56,4 +56,9 @@ export class SnapshotStorage {
snapshotsForTest() {
return [...this._frameSnapshots.keys()];
}
finalize() {
// Resources are not necessarily sorted in the trace file, so sort them now.
this._resources.sort((a, b) => (a._monotonicTime || 0) - (b._monotonicTime || 0));
}
}

View file

@ -142,7 +142,7 @@ async function doFetch(event: FetchEvent): Promise<Response> {
const lookupUrls = [request.url];
if (isDeployedAsHttps && request.url.startsWith('https://'))
lookupUrls.push(request.url.replace(/^https/, 'http'));
return snapshotServer.serveResource(lookupUrls, snapshotUrl);
return snapshotServer.serveResource(lookupUrls, request.method, snapshotUrl);
}
async function gc() {

View file

@ -100,6 +100,8 @@ export class TraceModel {
this.contextEntries.push(contextEntry);
}
this._snapshotStorage!.finalize();
}
async hasEntry(filename: string): Promise<boolean> {

View file

@ -98,7 +98,7 @@ export function suppressCertificateWarning() {
};
}
export async function parseTraceRaw(file: string): Promise<{ events: any[], resources: Map<string, Buffer>, actions: string[], stacks: Map<string, StackFrame[]> }> {
export async function parseTraceRaw(file: string): Promise<{ events: any[], resources: Map<string, Buffer>, actions: string[], actionObjects: ActionTraceEvent[], stacks: Map<string, StackFrame[]> }> {
const zipFS = new ZipFile(file);
const resources = new Map<string, Buffer>();
for (const entry of await zipFS.entries())
@ -111,6 +111,8 @@ export async function parseTraceRaw(file: string): Promise<{ events: any[], reso
for (const line of resources.get(traceFile)!.toString().split('\n')) {
if (line) {
const event = JSON.parse(line) as TraceEvent;
events.push(event);
if (event.type === 'before') {
const action: ActionTraceEvent = {
...event,
@ -118,7 +120,6 @@ export async function parseTraceRaw(file: string): Promise<{ events: any[], reso
endTime: 0,
log: []
};
events.push(action);
actionMap.set(event.callId, action);
} else if (event.type === 'input') {
const existing = actionMap.get(event.callId);
@ -131,8 +132,6 @@ export async function parseTraceRaw(file: string): Promise<{ events: any[], reso
existing.log = event.log;
existing.error = event.error;
existing.result = event.result;
} else {
events.push(event);
}
}
}
@ -151,21 +150,17 @@ export async function parseTraceRaw(file: string): Promise<{ events: any[], reso
stacks.set(key, value);
}
const actionObjects = [...actionMap.values()];
actionObjects.sort((a, b) => a.startTime - b.startTime);
return {
events,
resources,
actions: eventsToActions(events),
actions: actionObjects.map(a => a.apiName),
actionObjects,
stacks,
};
}
function eventsToActions(events: ActionTraceEvent[]): string[] {
// Trace viewer only shows non-internal non-tracing actions.
return events.filter(e => e.type === 'action')
.sort((a, b) => a.startTime - b.startTime)
.map(e => e.apiName);
}
export async function parseTrace(file: string): Promise<{ resources: Map<string, Buffer>, events: EventTraceEvent[], actions: ActionTraceEvent[], apiNames: string[], traceModel: TraceModel, model: MultiTraceModel, actionTree: string[] }> {
const backend = new TraceBackend(file);
const traceModel = new TraceModel();

View file

@ -51,7 +51,7 @@ it.describe('snapshots', () => {
});
await page.setContent('<link rel="stylesheet" href="style.css"><button>Hello</button>');
const snapshot = await snapshotter.captureSnapshot(toImpl(page), 'call@1', 'snapshot@call@1');
const resource = snapshot.resourceByUrl(`http://localhost:${server.PORT}/style.css`);
const resource = snapshot.resourceByUrl(`http://localhost:${server.PORT}/style.css`, 'GET');
expect(resource).toBeTruthy();
});
@ -124,7 +124,7 @@ it.describe('snapshots', () => {
await page.evaluate(() => { (document.styleSheets[0].cssRules[0] as any).style.color = 'blue'; });
const snapshot2 = await snapshotter.captureSnapshot(toImpl(page), 'call@2', 'snapshot@call@2');
const resource = snapshot2.resourceByUrl(`http://localhost:${server.PORT}/style.css`);
const resource = snapshot2.resourceByUrl(`http://localhost:${server.PORT}/style.css`, 'GET');
expect((await snapshotter.resourceContentForTest(resource.response.content._sha1)).toString()).toBe('button { color: blue; }');
});

View file

@ -851,7 +851,7 @@ test('should open trace-1.31', async ({ showTraceViewer }) => {
await expect(snapshot.locator('[__playwright_target__]')).toHaveText(['Submit']);
});
test('should prefer later resource request', async ({ page, server, runAndTrace }) => {
test('should prefer later resource request with the same method', async ({ page, server, runAndTrace }) => {
const html = `
<body>
<script>
@ -862,13 +862,22 @@ test('should prefer later resource request', async ({ page, server, runAndTrace
if (!window.location.href.includes('reloaded'))
window.location.href = window.location.href + '?reloaded';
else
link.onload = () => fetch('style.css', { method: 'HEAD' });
</script>
<div>Hello</div>
</body>
`;
let reloadStartedCallback = () => {};
const reloadStartedPromise = new Promise<void>(f => reloadStartedCallback = f);
server.setRoute('/style.css', async (req, res) => {
if (req.method === 'HEAD') {
res.statusCode = 200;
res.end('');
return;
}
// Make sure reload happens before style arrives.
await reloadStartedPromise;
res.end('body { background-color: rgb(123, 123, 123) }');
@ -880,8 +889,13 @@ test('should prefer later resource request', async ({ page, server, runAndTrace
});
const traceViewer = await runAndTrace(async () => {
const headRequest = page.waitForRequest(req => req.url() === server.PREFIX + '/style.css' && req.method() === 'HEAD');
await page.goto(server.PREFIX + '/index.html');
await headRequest;
await page.locator('div').click();
});
const frame = await traceViewer.snapshotFrame('page.goto');
await expect(frame.locator('body')).toHaveCSS('background-color', 'rgb(123, 123, 123)');
const frame1 = await traceViewer.snapshotFrame('page.goto');
await expect(frame1.locator('body')).toHaveCSS('background-color', 'rgb(123, 123, 123)');
const frame2 = await traceViewer.snapshotFrame('locator.click');
await expect(frame2.locator('body')).toHaveCSS('background-color', 'rgb(123, 123, 123)');
});

View file

@ -112,8 +112,8 @@ test('should not include buffers in the trace', async ({ context, page, server,
await page.goto(server.PREFIX + '/empty.html');
await page.screenshot();
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
const { events } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
const screenshotEvent = events.find(e => e.type === 'action' && e.apiName === 'page.screenshot');
const { actionObjects } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
const screenshotEvent = actionObjects.find(a => a.apiName === 'page.screenshot');
expect(screenshotEvent.beforeSnapshot).toBeTruthy();
expect(screenshotEvent.afterSnapshot).toBeTruthy();
expect(screenshotEvent.result).toEqual({});
@ -526,7 +526,7 @@ test('should hide internal stack frames', async ({ context, page }, testInfo) =>
await context.tracing.stop({ path: tracePath });
const trace = await parseTraceRaw(tracePath);
const actions = trace.events.filter(e => e.type === 'action' && !e.apiName.startsWith('tracing.'));
const actions = trace.actionObjects.filter(a => !a.apiName.startsWith('tracing.'));
expect(actions).toHaveLength(4);
for (const action of actions)
expect(relativeStack(action, trace.stacks)).toEqual(['tracing.spec.ts']);
@ -547,7 +547,7 @@ test('should hide internal stack frames in expect', async ({ context, page }, te
await context.tracing.stop({ path: tracePath });
const trace = await parseTraceRaw(tracePath);
const actions = trace.events.filter(e => e.type === 'action' && !e.apiName.startsWith('tracing.'));
const actions = trace.actionObjects.filter(a => !a.apiName.startsWith('tracing.'));
expect(actions).toHaveLength(5);
for (const action of actions)
expect(relativeStack(action, trace.stacks)).toEqual(['tracing.spec.ts']);
@ -703,6 +703,66 @@ test('should flush console events on tracing stop', async ({ context, page }, te
expect(events).toHaveLength(100);
});
test('should not emit after w/o before', async ({ browserType, mode }, testInfo) => {
test.skip(mode === 'service', 'Service ignores tracesDir');
const tracesDir = testInfo.outputPath('traces');
const browser = await browserType.launch({ tracesDir });
const context = await browser.newContext();
const page = await context.newPage();
await context.tracing.start({ name: 'name1', snapshots: true });
const evaluatePromise = page.evaluate(() => new Promise(f => (window as any).callback = f)).catch(() => {});
await context.tracing.stopChunk({ path: testInfo.outputPath('trace1.zip') });
expect(fs.existsSync(path.join(tracesDir, 'name1.trace'))).toBe(true);
await context.tracing.startChunk({ name: 'name2' });
await page.evaluateHandle(() => (window as any).callback());
await evaluatePromise;
await context.tracing.stop({ path: testInfo.outputPath('trace2.zip') });
expect(fs.existsSync(path.join(tracesDir, 'name2.trace'))).toBe(true);
await browser.close();
let minCallId = 100000;
const sanitize = (e: any) => {
if (e.type === 'after' || e.type === 'before') {
minCallId = Math.min(minCallId, +e.callId.split('@')[1]);
return {
type: e.type,
callId: +e.callId.split('@')[1] - minCallId,
apiName: e.apiName,
};
}
};
{
const { events } = await parseTraceRaw(testInfo.outputPath('trace1.zip'));
expect(events.map(sanitize).filter(Boolean)).toEqual([
{
type: 'before',
callId: 0,
apiName: 'page.evaluate'
}
]);
}
{
const { events } = await parseTraceRaw(testInfo.outputPath('trace2.zip'));
expect(events.map(sanitize).filter(Boolean)).toEqual([
{
type: 'before',
callId: 6,
apiName: 'page.evaluateHandle'
},
{
type: 'after',
callId: 6,
apiName: undefined
}
]);
}
});
function expectRed(pixels: Buffer, offset: number) {
const r = pixels.readUInt8(offset);
const g = pixels.readUInt8(offset + 1);

View file

@ -91,12 +91,14 @@ it('should work with glob', async () => {
expect(globToRegex('http://localhost:3000/signin-oidc*').test('http://localhost:3000/signin-oidc/foo')).toBeFalsy();
expect(globToRegex('http://localhost:3000/signin-oidc*').test('http://localhost:3000/signin-oidcnice')).toBeTruthy();
expect(globToRegex('**/three-columns/settings.html?**id=[a-z]**').test('http://mydomain:8080/blah/blah/three-columns/settings.html?id=settings-e3c58efe-02e9-44b0-97ac-dd138100cf7c&blah')).toBeTruthy();
expect(globToRegex('\\?')).toEqual(/^\?$/);
expect(globToRegex('\\')).toEqual(/^\\$/);
expect(globToRegex('\\\\')).toEqual(/^\\$/);
expect(globToRegex('\\[')).toEqual(/^\[$/);
expect(globToRegex('[')).toEqual(/^\[$/);
expect(globToRegex('$^+.\\*()|\\?\\{\\}[]')).toEqual(/^\$\^\+\.\*\(\)\|\?\{\}\[\]$/);
expect(globToRegex('[a-z]')).toEqual(/^[a-z]$/);
expect(globToRegex('$^+.\\*()|\\?\\{\\}\\[\\]')).toEqual(/^\$\^\+\.\*\(\)\|\?\{\}\[\]$/);
});
it('should intercept network activity from worker', async function({ page, server, isAndroid, browserName, browserMajorVersion }) {

View file

@ -54,7 +54,7 @@ const testFiles = {
await page.click('text=Click me');
});
test('shared failing', async ({ }) => {
test('shared failing', async ({ }) => {
await page.click('text=And me');
expect(1).toBe(2);
});

View file

@ -235,7 +235,7 @@ test(`testInfo.attach name should be sanitized`, async ({ runInlineTest }) => {
expect(result.failed).toBe(1);
expect(result.output).toContain('attachment #1: ../../../test (text/plain)');
expect(result.output).toContain(`attachments${path.sep}test-8d909b-`);
expect(result.output).toContain(`attachments${path.sep}-test`);
});
test(`testInfo.attach name can be empty string`, async ({ runInlineTest }) => {

View file

@ -48,7 +48,7 @@ test('should use project name', async ({ runInlineTest }, testInfo) => {
test('passes', async ({ page }, testInfo) => {});
`,
}, { reporter: 'dot,' + kRawReporterPath });
const json = JSON.parse(fs.readFileSync(testInfo.outputPath('output', 'report', 'project-name-656a9b.report'), 'utf-8'));
const json = JSON.parse(fs.readFileSync(testInfo.outputPath('output', 'report', 'project-name.report'), 'utf-8'));
expect(json.project.name).toBe('project-name');
expect(result.exitCode).toBe(0);
});
@ -173,7 +173,7 @@ test(`testInfo.attach should save attachments via path`, async ({ runInlineTest
expect(result.attachments[0].name).toBe('example.png');
expect(result.attachments[0].contentType).toBe('x-playwright/custom');
const p = result.attachments[0].path;
expect(p).toMatch(/[/\\]attachments[/\\]example-png-[0-9a-f]{6}-[0-9a-f]+\.json$/);
expect(p).toMatch(/[/\\]attachments[/\\]example-png-[0-9a-f]+\.json$/);
const contents = fs.readFileSync(p);
expect(contents.toString()).toBe('We <3 Playwright!');
}
@ -255,5 +255,5 @@ test('dupe project names', async ({ runInlineTest }, testInfo) => {
`,
}, { reporter: 'dot,' + kRawReporterPath });
const files = fs.readdirSync(testInfo.outputPath('test-results', 'report'));
expect(new Set(files)).toEqual(new Set(['project-name-656a9b.report', 'project-name-656a9b-1.report', 'project-name-656a9b-2.report']));
expect(new Set(files)).toEqual(new Set(['project-name.report', 'project-name-1.report', 'project-name-2.report']));
});

View file

@ -194,10 +194,10 @@ test('should include the project name', async ({ runInlineTest }) => {
expect(result.output).toContain('my-test.spec.js-snapshots/bar-foo.txt');
// test1, run with bar
expect(result.output).toContain('test-results/my-test-test-1-Bar-space-dc1461/bar.txt');
expect(result.output).toContain('my-test.spec.js-snapshots/bar-Bar-space-dc1461.txt');
expect(result.output).toContain('test-results/my-test-test-1-Bar-space-dc1461-retry1/bar.txt');
expect(result.output).toContain('my-test.spec.js-snapshots/bar-Bar-space-dc1461.txt');
expect(result.output).toContain('test-results/my-test-test-1-Bar-space-/bar.txt');
expect(result.output).toContain('my-test.spec.js-snapshots/bar-Bar-space-.txt');
expect(result.output).toContain('test-results/my-test-test-1-Bar-space--retry1/bar.txt');
expect(result.output).toContain('my-test.spec.js-snapshots/bar-Bar-space-.txt');
// test2, run with empty
expect(result.output).toContain('test-results/my-test-test-2/bar.txt');
@ -212,8 +212,8 @@ test('should include the project name', async ({ runInlineTest }) => {
expect(result.output).toContain('my-test.spec.js-snapshots/bar-foo-suffix.txt');
// test2, run with bar
expect(result.output).toContain('test-results/my-test-test-2-Bar-space-dc1461/bar.txt');
expect(result.output).toContain('my-test.spec.js-snapshots/bar-Bar-space-dc1461-suffix.txt');
expect(result.output).toContain('test-results/my-test-test-2-Bar-space-/bar.txt');
expect(result.output).toContain('my-test.spec.js-snapshots/bar-Bar-space--suffix.txt');
});
test('should include path option in snapshot', async ({ runInlineTest }) => {
@ -401,58 +401,6 @@ test('should allow nonAscii characters in the output dir', async ({ runInlineTes
expect(outputDir).toBe(path.join(testInfo.outputDir, 'test-results', 'my-test-こんにちは世界'));
});
test('should not collide with other tests when nonAscii characters are replaced', async ({ runInlineTest }) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23386' });
const result = await runInlineTest({
'my-test.spec.js': `
import { test, expect } from '@playwright/test';
const specialChars = ['>', '=', '<', '+', '#', '-', '.', '!', '$', '%', '&', '\\'', '*', '/', '?', '^', '_', '\`', '{', '|', '}', '~', '(', ')', '[', ']', '@'];
for (const char of specialChars) {
test('test' + char, async ({}, testInfo) => {
console.log('\\n%%' + testInfo.outputDir);
});
}
`,
});
expect(result.exitCode).toBe(0);
const outputDirs = result.outputLines;
expect(outputDirs.length).toBe(27);
expect(new Set(outputDirs).size).toBe(outputDirs.length);
const forbiddenCharacters = ['\\', '/', ':', '*', '?', '"', '<', '>', '|', '%'];
for (const outputDir of outputDirs) {
const relativePath = path.relative(path.join(test.info().outputDir, 'test-results'), outputDir);
for (const forbiddenCharacter of forbiddenCharacters)
expect(relativePath).not.toContain(forbiddenCharacter);
}
});
test('should generate expected output dir names', async ({ runInlineTest }, testInfo) => {
const runTests = async (fileName: string, testNames: string[]) => {
const result = await runInlineTest({
[fileName]: `
import { test, expect } from '@playwright/test';
${testNames.map(name => `test('${name}', async ({}, testInfo) => console.log('\\n%%' + testInfo.outputDir));`).join('\n')}
`,
});
expect(result.exitCode).toBe(0);
const outputDirs = result.outputLines.map(line => path.relative(path.join(testInfo.outputDir, 'test-results'), line));
return outputDirs;
};
expect(await runTests('filename.spec.js', [
'testing > foo',
'testing multiple spaces',
'testing multiple spaces',
'!!!hello!!!',
'dashes-are-used',
])).toEqual([
'filename-testing-foo-574286',
'filename-testing-multiple-spaces',
'filename-testing-multiple-spaces-f5d359',
'filename-hello-8eb257',
'filename-dashes-are-used-c67e31',
]);
});
test('should allow shorten long output dirs characters in the output dir', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
'very/deep/and/long/file/name/that/i/want/to/be/trimmed/my-test.spec.js': `
@ -478,7 +426,7 @@ test('should not mangle double dashes', async ({ runInlineTest }, testInfo) => {
`,
});
const outputDir = result.outputLines[0];
expect(outputDir).toBe(path.join(testInfo.outputDir, 'test-results', 'my--file-my-test-68b7dd'));
expect(outputDir).toBe(path.join(testInfo.outputDir, 'test-results', 'my--file-my--test'));
});
test('should allow include the describe name the output dir', async ({ runInlineTest }, testInfo) => {

View file

@ -283,6 +283,7 @@ test('should support clip option for page', async ({ runInlineTest }, testInfo)
});
`
});
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-is-a-test'))).toBe(false);
expect(result.exitCode).toBe(0);
});