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 # 🎭 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) ## [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 | | | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: | | :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->115.0.5790.75<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: | | 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: | | 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. 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 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 ## Version 1.35
### Highlights ### Highlights

View file

@ -12,6 +12,21 @@ toc_max_heading_level: 2
import LiteYouTube from '@site/src/components/LiteYouTube'; 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 ## Version 1.35
### Highlights ### Highlights

View file

@ -6,6 +6,21 @@ toc_max_heading_level: 2
import LiteYouTube from '@site/src/components/LiteYouTube'; 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 ## Version 1.35
<LiteYouTube <LiteYouTube

View file

@ -4,6 +4,21 @@ title: "Release notes"
toc_max_heading_level: 2 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 ## Version 1.35
### Highlights ### Highlights

82
package-lock.json generated
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
{ {
"Blackberry PlayBook": { "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": { "viewport": {
"width": 600, "width": 600,
"height": 1024 "height": 1024
@ -11,7 +11,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Blackberry PlayBook landscape": { "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": { "viewport": {
"width": 1024, "width": 1024,
"height": 600 "height": 600
@ -22,7 +22,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"BlackBerry Z30": { "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": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -33,7 +33,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"BlackBerry Z30 landscape": { "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": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -44,7 +44,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Galaxy Note 3": { "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": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -55,7 +55,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Galaxy Note 3 landscape": { "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": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -66,7 +66,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Galaxy Note II": { "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": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -77,7 +77,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Galaxy Note II landscape": { "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": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -88,7 +88,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Galaxy S III": { "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": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -99,7 +99,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Galaxy S III landscape": { "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": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -198,7 +198,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"iPad (gen 6)": { "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": { "viewport": {
"width": 768, "width": 768,
"height": 1024 "height": 1024
@ -209,7 +209,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad (gen 6) landscape": { "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": { "viewport": {
"width": 1024, "width": 1024,
"height": 768 "height": 768
@ -220,7 +220,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad (gen 7)": { "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": { "viewport": {
"width": 810, "width": 810,
"height": 1080 "height": 1080
@ -231,7 +231,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad (gen 7) landscape": { "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": { "viewport": {
"width": 1080, "width": 1080,
"height": 810 "height": 810
@ -242,7 +242,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad Mini": { "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": { "viewport": {
"width": 768, "width": 768,
"height": 1024 "height": 1024
@ -253,7 +253,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad Mini landscape": { "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": { "viewport": {
"width": 1024, "width": 1024,
"height": 768 "height": 768
@ -264,7 +264,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad Pro 11": { "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": { "viewport": {
"width": 834, "width": 834,
"height": 1194 "height": 1194
@ -275,7 +275,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad Pro 11 landscape": { "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": { "viewport": {
"width": 1194, "width": 1194,
"height": 834 "height": 834
@ -286,7 +286,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 6": { "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": { "viewport": {
"width": 375, "width": 375,
"height": 667 "height": 667
@ -297,7 +297,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 6 landscape": { "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": { "viewport": {
"width": 667, "width": 667,
"height": 375 "height": 375
@ -308,7 +308,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 6 Plus": { "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": { "viewport": {
"width": 414, "width": 414,
"height": 736 "height": 736
@ -319,7 +319,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 6 Plus landscape": { "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": { "viewport": {
"width": 736, "width": 736,
"height": 414 "height": 414
@ -330,7 +330,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 7": { "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": { "viewport": {
"width": 375, "width": 375,
"height": 667 "height": 667
@ -341,7 +341,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 7 landscape": { "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": { "viewport": {
"width": 667, "width": 667,
"height": 375 "height": 375
@ -352,7 +352,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 7 Plus": { "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": { "viewport": {
"width": 414, "width": 414,
"height": 736 "height": 736
@ -363,7 +363,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 7 Plus landscape": { "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": { "viewport": {
"width": 736, "width": 736,
"height": 414 "height": 414
@ -374,7 +374,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 8": { "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": { "viewport": {
"width": 375, "width": 375,
"height": 667 "height": 667
@ -385,7 +385,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 8 landscape": { "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": { "viewport": {
"width": 667, "width": 667,
"height": 375 "height": 375
@ -396,7 +396,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 8 Plus": { "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": { "viewport": {
"width": 414, "width": 414,
"height": 736 "height": 736
@ -407,7 +407,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 8 Plus landscape": { "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": { "viewport": {
"width": 736, "width": 736,
"height": 414 "height": 414
@ -418,7 +418,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone SE": { "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": { "viewport": {
"width": 320, "width": 320,
"height": 568 "height": 568
@ -429,7 +429,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone SE landscape": { "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": { "viewport": {
"width": 568, "width": 568,
"height": 320 "height": 320
@ -440,7 +440,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone X": { "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": { "viewport": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -451,7 +451,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone X landscape": { "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": { "viewport": {
"width": 812, "width": 812,
"height": 375 "height": 375
@ -462,7 +462,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone XR": { "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": { "viewport": {
"width": 414, "width": 414,
"height": 896 "height": 896
@ -473,7 +473,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone XR landscape": { "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": { "viewport": {
"width": 896, "width": 896,
"height": 414 "height": 414
@ -484,7 +484,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 11": { "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": { "screen": {
"width": 414, "width": 414,
"height": 896 "height": 896
@ -499,7 +499,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 11 landscape": { "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": { "screen": {
"width": 414, "width": 414,
"height": 896 "height": 896
@ -514,7 +514,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 11 Pro": { "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": { "screen": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -529,7 +529,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 11 Pro landscape": { "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": { "screen": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -544,7 +544,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 11 Pro Max": { "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": { "screen": {
"width": 414, "width": 414,
"height": 896 "height": 896
@ -559,7 +559,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 11 Pro Max landscape": { "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": { "screen": {
"width": 414, "width": 414,
"height": 896 "height": 896
@ -574,7 +574,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12": { "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": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -589,7 +589,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 landscape": { "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": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -604,7 +604,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 Pro": { "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": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -619,7 +619,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 Pro landscape": { "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": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -634,7 +634,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 Pro Max": { "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": { "screen": {
"width": 428, "width": 428,
"height": 926 "height": 926
@ -649,7 +649,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 Pro Max landscape": { "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": { "screen": {
"width": 428, "width": 428,
"height": 926 "height": 926
@ -664,7 +664,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 Mini": { "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": { "screen": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -679,7 +679,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 Mini landscape": { "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": { "screen": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -694,7 +694,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13": { "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": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -709,7 +709,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 landscape": { "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": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -724,7 +724,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 Pro": { "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": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -739,7 +739,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 Pro landscape": { "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": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -754,7 +754,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 Pro Max": { "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": { "screen": {
"width": 428, "width": 428,
"height": 926 "height": 926
@ -769,7 +769,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 Pro Max landscape": { "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": { "screen": {
"width": 428, "width": 428,
"height": 926 "height": 926
@ -784,7 +784,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 Mini": { "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": { "screen": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -799,7 +799,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 Mini landscape": { "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": { "screen": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -1315,7 +1315,7 @@
"defaultBrowserType": "firefox" "defaultBrowserType": "firefox"
}, },
"Desktop Safari": { "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": { "screen": {
"width": 1792, "width": 1792,
"height": 1120 "height": 1120

View file

@ -437,6 +437,12 @@ export class HarTracer {
const pageEntry = this._createPageEntryIfNeeded(page); const pageEntry = this._createPageEntryIfNeeded(page);
const request = response.request(); 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 = { harEntry.response = {
status: response.status(), status: response.status(),
statusText: response.statusText(), statusText: response.statusText(),

View file

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

View file

@ -33,8 +33,8 @@ import { WKPage } from './wkPage';
import { kBrowserClosedError } from '../../common/errors'; import { kBrowserClosedError } from '../../common/errors';
import type { SdkObject } from '../instrumentation'; 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 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 = '16.4'; const BROWSER_VERSION = '17.0';
export class WKBrowser extends Browser { export class WKBrowser extends Browser {
private readonly _connection: WKConnection; private readonly _connection: WKConnection;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
{ {
"name": "@playwright/test", "name": "@playwright/test",
"version": "1.36.0-next", "version": "1.36.2",
"description": "A high-level API to automate web browsers", "description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -41,7 +41,7 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"playwright-core": "1.36.0-next" "playwright-core": "1.36.2"
}, },
"optionalDependencies": { "optionalDependencies": {
"fsevents": "2.3.2" "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 { getComparator } from 'playwright-core/lib/utils';
import type { PageScreenshotOptions } from 'playwright-core/types/types'; import type { PageScreenshotOptions } from 'playwright-core/types/types';
import { import {
serializeError, sanitizeForFilePath, addSuffixToFilePath, serializeError, sanitizeForFilePath,
trimLongString, callLogText, trimLongString, callLogText,
expectTypes } from '../util'; expectTypes } from '../util';
import { colors } from 'playwright-core/lib/utilsBundle'; 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 inputPathSegments = Array.isArray(name) ? name : [addSuffixToFilePath(name, '', undefined, true)];
const outputPathSegments = Array.isArray(name) ? name : [addSuffixToFilePath(name, actualModifier, undefined, true)]; const outputPathSegments = Array.isArray(name) ? name : [addSuffixToFilePath(name, actualModifier, undefined, true)];
this.snapshotPath = snapshotPathResolver(...inputPathSegments); this.snapshotPath = snapshotPathResolver(...inputPathSegments);
const inputFile = testInfo.outputPath(...inputPathSegments); const inputFile = testInfo._getOutputPath(...inputPathSegments);
const outputFile = testInfo.outputPath(...outputPathSegments); const outputFile = testInfo._getOutputPath(...outputPathSegments);
this.expectedPath = addSuffixToFilePath(inputFile, '-expected'); this.expectedPath = addSuffixToFilePath(inputFile, '-expected');
this.previousPath = addSuffixToFilePath(outputFile, '-previous'); this.previousPath = addSuffixToFilePath(outputFile, '-previous');
this.actualPath = addSuffixToFilePath(outputFile, '-actual'); this.actualPath = addSuffixToFilePath(outputFile, '-actual');
@ -440,14 +440,3 @@ function determineFileExtension(file: string | Buffer): string {
function compareMagicBytes(file: Buffer, magicBytes: number[]): boolean { function compareMagicBytes(file: Buffer, magicBytes: number[]): boolean {
return Buffer.compare(Buffer.from(magicBytes), file.slice(0, magicBytes.length)) === 0; 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) { export function sanitizeForFilePath(s: string) {
let nonTrivialSubstitute = false; return s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-');
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 trimLongString(s: string, length = 100) { 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); 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. * Returns absolute path contained within parent directory.
*/ */

View file

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

View file

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

View file

@ -1,6 +1,6 @@
{ {
"name": "playwright", "name": "playwright",
"version": "1.36.0-next", "version": "1.36.2",
"description": "A high-level API to automate web browsers", "description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -28,6 +28,6 @@
"install": "node install.js" "install": "node install.js"
}, },
"dependencies": { "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 }; 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; const snapshot = this._snapshot;
let result: ResourceSnapshot | undefined; let result: ResourceSnapshot | undefined;
// First try locating exact resource belonging to this frame. // First try locating exact resource belonging to this frame.
for (const resource of this._resources) { 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) if (typeof resource._monotonicTime === 'number' && resource._monotonicTime >= snapshot.timestamp)
break; break;
if (resource._frameref !== snapshot.frameId) if (resource._frameref !== snapshot.frameId)
continue; 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 // 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. // at the time of snapshot, not the earlier aborted resource with the same url.
result = resource; result = resource;
@ -132,9 +134,11 @@ export class SnapshotRenderer {
if (!result) { if (!result) {
// Then fall back to resource with this URL to account for memory cache. // Then fall back to resource with this URL to account for memory cache.
for (const resource of this._resources) { 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) if (typeof resource._monotonicTime === 'number' && resource._monotonicTime >= snapshot.timestamp)
break; 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 // 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. // at the time of snapshot, not the earlier aborted resource with the same url.
result = resource; result = resource;
@ -142,7 +146,7 @@ export class SnapshotRenderer {
} }
} }
if (result) { if (result && method.toUpperCase() === 'GET') {
// Patch override if necessary. // Patch override if necessary.
for (const o of snapshot.resourceOverrides) { for (const o of snapshot.resourceOverrides) {
if (url === o.url && o.sha1) { 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; let resource: ResourceSnapshot | undefined;
const snapshot = this._snapshotIds.get(snapshotUrl)!; const snapshot = this._snapshotIds.get(snapshotUrl)!;
for (const requestUrl of requestUrlAlternatives) { for (const requestUrl of requestUrlAlternatives) {
resource = snapshot?.resourceByUrl(removeHash(requestUrl)); resource = snapshot?.resourceByUrl(removeHash(requestUrl), method);
if (resource) if (resource)
break; break;
} }

View file

@ -56,4 +56,9 @@ export class SnapshotStorage {
snapshotsForTest() { snapshotsForTest() {
return [...this._frameSnapshots.keys()]; 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]; const lookupUrls = [request.url];
if (isDeployedAsHttps && request.url.startsWith('https://')) if (isDeployedAsHttps && request.url.startsWith('https://'))
lookupUrls.push(request.url.replace(/^https/, 'http')); lookupUrls.push(request.url.replace(/^https/, 'http'));
return snapshotServer.serveResource(lookupUrls, snapshotUrl); return snapshotServer.serveResource(lookupUrls, request.method, snapshotUrl);
} }
async function gc() { async function gc() {

View file

@ -100,6 +100,8 @@ export class TraceModel {
this.contextEntries.push(contextEntry); this.contextEntries.push(contextEntry);
} }
this._snapshotStorage!.finalize();
} }
async hasEntry(filename: string): Promise<boolean> { 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 zipFS = new ZipFile(file);
const resources = new Map<string, Buffer>(); const resources = new Map<string, Buffer>();
for (const entry of await zipFS.entries()) 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')) { for (const line of resources.get(traceFile)!.toString().split('\n')) {
if (line) { if (line) {
const event = JSON.parse(line) as TraceEvent; const event = JSON.parse(line) as TraceEvent;
events.push(event);
if (event.type === 'before') { if (event.type === 'before') {
const action: ActionTraceEvent = { const action: ActionTraceEvent = {
...event, ...event,
@ -118,7 +120,6 @@ export async function parseTraceRaw(file: string): Promise<{ events: any[], reso
endTime: 0, endTime: 0,
log: [] log: []
}; };
events.push(action);
actionMap.set(event.callId, action); actionMap.set(event.callId, action);
} else if (event.type === 'input') { } else if (event.type === 'input') {
const existing = actionMap.get(event.callId); 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.log = event.log;
existing.error = event.error; existing.error = event.error;
existing.result = event.result; 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); stacks.set(key, value);
} }
const actionObjects = [...actionMap.values()];
actionObjects.sort((a, b) => a.startTime - b.startTime);
return { return {
events, events,
resources, resources,
actions: eventsToActions(events), actions: actionObjects.map(a => a.apiName),
actionObjects,
stacks, 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[] }> { 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 backend = new TraceBackend(file);
const traceModel = new TraceModel(); 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>'); 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 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(); expect(resource).toBeTruthy();
}); });
@ -124,7 +124,7 @@ it.describe('snapshots', () => {
await page.evaluate(() => { (document.styleSheets[0].cssRules[0] as any).style.color = 'blue'; }); 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 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; }'); 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']); 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 = ` const html = `
<body> <body>
<script> <script>
@ -862,13 +862,22 @@ test('should prefer later resource request', async ({ page, server, runAndTrace
if (!window.location.href.includes('reloaded')) if (!window.location.href.includes('reloaded'))
window.location.href = window.location.href + '?reloaded'; window.location.href = window.location.href + '?reloaded';
else
link.onload = () => fetch('style.css', { method: 'HEAD' });
</script> </script>
<div>Hello</div>
</body> </body>
`; `;
let reloadStartedCallback = () => {}; let reloadStartedCallback = () => {};
const reloadStartedPromise = new Promise<void>(f => reloadStartedCallback = f); const reloadStartedPromise = new Promise<void>(f => reloadStartedCallback = f);
server.setRoute('/style.css', async (req, res) => { server.setRoute('/style.css', async (req, res) => {
if (req.method === 'HEAD') {
res.statusCode = 200;
res.end('');
return;
}
// Make sure reload happens before style arrives. // Make sure reload happens before style arrives.
await reloadStartedPromise; await reloadStartedPromise;
res.end('body { background-color: rgb(123, 123, 123) }'); 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 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 page.goto(server.PREFIX + '/index.html');
await headRequest;
await page.locator('div').click();
}); });
const frame = await traceViewer.snapshotFrame('page.goto'); const frame1 = await traceViewer.snapshotFrame('page.goto');
await expect(frame.locator('body')).toHaveCSS('background-color', 'rgb(123, 123, 123)'); 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.goto(server.PREFIX + '/empty.html');
await page.screenshot(); await page.screenshot();
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') }); await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
const { events } = await parseTraceRaw(testInfo.outputPath('trace.zip')); const { actionObjects } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
const screenshotEvent = events.find(e => e.type === 'action' && e.apiName === 'page.screenshot'); const screenshotEvent = actionObjects.find(a => a.apiName === 'page.screenshot');
expect(screenshotEvent.beforeSnapshot).toBeTruthy(); expect(screenshotEvent.beforeSnapshot).toBeTruthy();
expect(screenshotEvent.afterSnapshot).toBeTruthy(); expect(screenshotEvent.afterSnapshot).toBeTruthy();
expect(screenshotEvent.result).toEqual({}); expect(screenshotEvent.result).toEqual({});
@ -526,7 +526,7 @@ test('should hide internal stack frames', async ({ context, page }, testInfo) =>
await context.tracing.stop({ path: tracePath }); await context.tracing.stop({ path: tracePath });
const trace = await parseTraceRaw(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); expect(actions).toHaveLength(4);
for (const action of actions) for (const action of actions)
expect(relativeStack(action, trace.stacks)).toEqual(['tracing.spec.ts']); 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 }); await context.tracing.stop({ path: tracePath });
const trace = await parseTraceRaw(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); expect(actions).toHaveLength(5);
for (const action of actions) for (const action of actions)
expect(relativeStack(action, trace.stacks)).toEqual(['tracing.spec.ts']); 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); 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) { function expectRed(pixels: Buffer, offset: number) {
const r = pixels.readUInt8(offset); const r = pixels.readUInt8(offset);
const g = pixels.readUInt8(offset + 1); 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-oidc/foo')).toBeFalsy();
expect(globToRegex('http://localhost:3000/signin-oidc*').test('http://localhost:3000/signin-oidcnice')).toBeTruthy(); 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('\\[')).toEqual(/^\[$/); expect(globToRegex('\\[')).toEqual(/^\[$/);
expect(globToRegex('[')).toEqual(/^\[$/); expect(globToRegex('[a-z]')).toEqual(/^[a-z]$/);
expect(globToRegex('$^+.\\*()|\\?\\{\\}[]')).toEqual(/^\$\^\+\.\*\(\)\|\?\{\}\[\]$/); expect(globToRegex('$^+.\\*()|\\?\\{\\}\\[\\]')).toEqual(/^\$\^\+\.\*\(\)\|\?\{\}\[\]$/);
}); });
it('should intercept network activity from worker', async function({ page, server, isAndroid, browserName, browserMajorVersion }) { it('should intercept network activity from worker', async function({ page, server, isAndroid, browserName, browserMajorVersion }) {

View file

@ -235,7 +235,7 @@ test(`testInfo.attach name should be sanitized`, async ({ runInlineTest }) => {
expect(result.failed).toBe(1); expect(result.failed).toBe(1);
expect(result.output).toContain('attachment #1: ../../../test (text/plain)'); 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 }) => { 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) => {}); test('passes', async ({ page }, testInfo) => {});
`, `,
}, { reporter: 'dot,' + kRawReporterPath }); }, { 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(json.project.name).toBe('project-name');
expect(result.exitCode).toBe(0); 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].name).toBe('example.png');
expect(result.attachments[0].contentType).toBe('x-playwright/custom'); expect(result.attachments[0].contentType).toBe('x-playwright/custom');
const p = result.attachments[0].path; 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); const contents = fs.readFileSync(p);
expect(contents.toString()).toBe('We <3 Playwright!'); expect(contents.toString()).toBe('We <3 Playwright!');
} }
@ -255,5 +255,5 @@ test('dupe project names', async ({ runInlineTest }, testInfo) => {
`, `,
}, { reporter: 'dot,' + kRawReporterPath }); }, { reporter: 'dot,' + kRawReporterPath });
const files = fs.readdirSync(testInfo.outputPath('test-results', 'report')); 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'); expect(result.output).toContain('my-test.spec.js-snapshots/bar-foo.txt');
// test1, run with bar // test1, run with bar
expect(result.output).toContain('test-results/my-test-test-1-Bar-space-dc1461/bar.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-dc1461.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-dc1461-retry1/bar.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-dc1461.txt'); expect(result.output).toContain('my-test.spec.js-snapshots/bar-Bar-space-.txt');
// test2, run with empty // test2, run with empty
expect(result.output).toContain('test-results/my-test-test-2/bar.txt'); 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'); expect(result.output).toContain('my-test.spec.js-snapshots/bar-foo-suffix.txt');
// test2, run with bar // test2, run with bar
expect(result.output).toContain('test-results/my-test-test-2-Bar-space-dc1461/bar.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-dc1461-suffix.txt'); expect(result.output).toContain('my-test.spec.js-snapshots/bar-Bar-space--suffix.txt');
}); });
test('should include path option in snapshot', async ({ runInlineTest }) => { 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-こんにちは世界')); 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) => { test('should allow shorten long output dirs characters in the output dir', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({ const result = await runInlineTest({
'very/deep/and/long/file/name/that/i/want/to/be/trimmed/my-test.spec.js': ` '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]; 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) => { 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); expect(result.exitCode).toBe(0);
}); });