Merge branch 'main' into watch-icon-focused
This commit is contained in:
commit
7ad93b85b6
|
|
@ -1,6 +1,6 @@
|
||||||
# 🎭 Playwright
|
# 🎭 Playwright
|
||||||
|
|
||||||
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop --> [](https://aka.ms/playwright/discord)
|
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop --> [](https://aka.ms/playwright/discord)
|
||||||
|
|
||||||
## [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)
|
||||||
|
|
||||||
|
|
@ -8,9 +8,9 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr
|
||||||
|
|
||||||
| | Linux | macOS | Windows |
|
| | Linux | macOS | Windows |
|
||||||
| :--- | :---: | :---: | :---: |
|
| :--- | :---: | :---: | :---: |
|
||||||
| Chromium <!-- GEN:chromium-version -->133.0.6943.35<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
| Chromium <!-- GEN:chromium-version -->134.0.6998.3<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||||
| WebKit <!-- GEN:webkit-version -->18.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
| WebKit <!-- GEN:webkit-version -->18.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||||
| Firefox <!-- GEN:firefox-version -->134.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
| Firefox <!-- GEN:firefox-version -->135.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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ Methods like [`method: APIRequestContext.get`] take the base URL into considerat
|
||||||
- `records` <[Array]<[Object]>>
|
- `records` <[Array]<[Object]>>
|
||||||
- `key` ?<[Object]>
|
- `key` ?<[Object]>
|
||||||
- `keyEncoded` ?<[Object]> if `key` is not JSON-serializable, this contains an encoded version that preserves types.
|
- `keyEncoded` ?<[Object]> if `key` is not JSON-serializable, this contains an encoded version that preserves types.
|
||||||
- `value` <[Object]>
|
- `value` ?<[Object]>
|
||||||
- `valueEncoded` ?<[Object]> if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
- `valueEncoded` ?<[Object]> if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
||||||
|
|
||||||
Populates context with given storage state. This option can be used to initialize context with logged-in information
|
Populates context with given storage state. This option can be used to initialize context with logged-in information
|
||||||
|
|
|
||||||
|
|
@ -897,7 +897,7 @@ context cookies from the response. The method will automatically follow redirect
|
||||||
- `records` <[Array]<[Object]>>
|
- `records` <[Array]<[Object]>>
|
||||||
- `key` ?<[Object]>
|
- `key` ?<[Object]>
|
||||||
- `keyEncoded` ?<[Object]> if `key` is not JSON-serializable, this contains an encoded version that preserves types.
|
- `keyEncoded` ?<[Object]> if `key` is not JSON-serializable, this contains an encoded version that preserves types.
|
||||||
- `value` <[Object]>
|
- `value` ?<[Object]>
|
||||||
- `valueEncoded` ?<[Object]> if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
- `valueEncoded` ?<[Object]> if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
||||||
|
|
||||||
Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to the constructor.
|
Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to the constructor.
|
||||||
|
|
|
||||||
|
|
@ -1528,7 +1528,7 @@ Whether to emulate network being offline for the browser context.
|
||||||
- `records` <[Array]<[Object]>>
|
- `records` <[Array]<[Object]>>
|
||||||
- `key` ?<[Object]>
|
- `key` ?<[Object]>
|
||||||
- `keyEncoded` ?<[Object]> if `key` is not JSON-serializable, this contains an encoded version that preserves types.
|
- `keyEncoded` ?<[Object]> if `key` is not JSON-serializable, this contains an encoded version that preserves types.
|
||||||
- `value` <[Object]>
|
- `value` ?<[Object]>
|
||||||
- `valueEncoded` ?<[Object]> if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
- `valueEncoded` ?<[Object]> if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
||||||
|
|
||||||
Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
|
Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
|
||||||
|
|
|
||||||
|
|
@ -281,7 +281,7 @@ Specify environment variables that will be visible to the browser. Defaults to `
|
||||||
- `records` <[Array]<[Object]>>
|
- `records` <[Array]<[Object]>>
|
||||||
- `key` ?<[Object]>
|
- `key` ?<[Object]>
|
||||||
- `keyEncoded` ?<[Object]> if `key` is not JSON-serializable, this contains an encoded version that preserves types.
|
- `keyEncoded` ?<[Object]> if `key` is not JSON-serializable, this contains an encoded version that preserves types.
|
||||||
- `value` <[Object]>
|
- `value` ?<[Object]>
|
||||||
- `valueEncoded` ?<[Object]> if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
- `valueEncoded` ?<[Object]> if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
||||||
|
|
||||||
Learn more about [storage state and auth](../auth.md).
|
Learn more about [storage state and auth](../auth.md).
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,13 @@ UI Mode lets you explore, run, and debug tests with a time travel experience com
|
||||||
|
|
||||||
To open UI mode, run the following command in your terminal:
|
To open UI mode, run the following command in your terminal:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx playwright test --ui
|
npx playwright test --ui
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running your tests
|
## Running your tests
|
||||||
|
|
||||||
Once you launch UI Mode you will see a list of all your test files. You can run all your tests by clicking the triangle icon in the sidebar. You can also run a single test file, a block of tests or a single test by hovering over the name and clicking on the triangle next to it.
|
Once you launch UI Mode you will see a list of all your test files. You can run all your tests by clicking the triangle icon in the sidebar. You can also run a single test file, a block of tests or a single test by hovering over the name and clicking on the triangle next to it.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
@ -33,17 +34,15 @@ Filter tests by text or `@tag` or by passed, failed or skipped tests. You can al
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Timeline view
|
## Timeline view
|
||||||
|
|
||||||
At the top of the trace you can see a timeline view of your test with different colors to highlight navigation and actions. Hover back and forth to see an image snapshot for each action. Double click on an action to see the time range for that action. You can use the slider in the timeline to increase the actions selected and these will be shown in the Actions tab and all console logs and network logs will be filtered to only show the logs for the actions selected.
|
At the top of the trace you can see a timeline view of your test with different colors to highlight navigation and actions. Hover back and forth to see an image snapshot for each action. Double click on an action to see the time range for that action. You can use the slider in the timeline to increase the actions selected and these will be shown in the Actions tab and all console logs and network logs will be filtered to only show the logs for the actions selected.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Actions
|
## Actions
|
||||||
|
|
||||||
In the Actions tab you can see what locator was used for every action and how long each one took to run. Hover over each action of your test and visually see the change in the DOM snapshot. Go back and forward in time and click an action to inspect and debug. Use the Before and After tabs to visually see what happened before and after the action.
|
In the Actions tab you can see what locator was used for every action and how long each one took to run. Hover over each action of your test and visually see the change in the DOM snapshot. Go back and forward in time and click an action to inspect and debug. Use the Before and After tabs to visually see what happened before and after the action.
|
||||||

|

|
||||||
|
|
||||||
## Pop out and inspect the DOM
|
## Pop out and inspect the DOM
|
||||||
|
|
@ -60,7 +59,7 @@ Click on the pick locator button and hover over the DOM snapshot to see the loca
|
||||||
|
|
||||||
## Source
|
## Source
|
||||||
|
|
||||||
As you hover over each action of your test the line of code for that action is highlighted in the source panel.
|
As you hover over each action of your test the line of code for that action is highlighted in the source panel. The button "Open in VSCode" is at the top-right of this section. Upon clicking the button, it will open your test in VS Code right at the line of code that you clicked on.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
@ -108,7 +107,7 @@ Next to the Actions tab you will find the Metadata tab which will show you more
|
||||||
|
|
||||||
## Watch mode
|
## Watch mode
|
||||||
|
|
||||||
Next to the name of each test in the sidebar you will find an eye icon. Clicking on the icon will activate watch mode which will re-run the test when you make changes to it. You can watch a number of tests at the same time be clicking the eye icon next to each one or all tests by clicking the eye icon at the top of the sidebar. If you are using VS Code then you can easily open your test by clicking on the file icon next to the eye icon. This will open your test in VS Code right at the line of code that you clicked on.
|
Next to the name of each test in the sidebar you will find an eye icon. Clicking on the icon will activate watch mode which will re-run the test when you make changes to it. You can watch a number of tests at the same time be clicking the eye icon next to each one or all tests by clicking the eye icon at the top of the sidebar.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
|
||||||
315
package-lock.json
generated
315
package-lock.json
generated
|
|
@ -50,7 +50,7 @@
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"electron": "^30.1.2",
|
"electron": "^30.1.2",
|
||||||
"esbuild": "^0.18.11",
|
"esbuild": "^0.25.0",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-notice": "^1.0.0",
|
"eslint-plugin-notice": "^1.0.0",
|
||||||
|
|
@ -885,355 +885,411 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
|
||||||
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
|
"integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-arm64": {
|
"node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
|
||||||
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
|
"integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/android-x64": {
|
"node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
|
"integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
|
||||||
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
|
"integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
|
"integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
|
||||||
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
|
"integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"freebsd"
|
"freebsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/freebsd-x64": {
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
|
"integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"freebsd"
|
"freebsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm": {
|
"node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
|
||||||
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
|
"integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-arm64": {
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
|
||||||
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
|
"integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ia32": {
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
|
||||||
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
|
"integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-loong64": {
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
|
||||||
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
|
"integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-mips64el": {
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
|
||||||
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
|
"integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-ppc64": {
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
|
||||||
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
|
"integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-riscv64": {
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
|
||||||
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
|
"integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-s390x": {
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
|
||||||
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
|
"integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/linux-x64": {
|
"node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
|
"integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/netbsd-x64": {
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
|
||||||
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
|
"integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"netbsd"
|
"netbsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/openbsd-x64": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
|
"integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"openbsd"
|
"openbsd"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/sunos-x64": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
|
"integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"sunos"
|
"sunos"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-arm64": {
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
|
||||||
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
|
"integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-ia32": {
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
|
||||||
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
|
"integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
|
||||||
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
|
"integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint-community/eslint-utils": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
|
|
@ -3857,40 +3913,61 @@
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.18.20",
|
"version": "0.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
|
||||||
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
|
"integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/android-arm": "0.18.20",
|
"@esbuild/aix-ppc64": "0.25.0",
|
||||||
"@esbuild/android-arm64": "0.18.20",
|
"@esbuild/android-arm": "0.25.0",
|
||||||
"@esbuild/android-x64": "0.18.20",
|
"@esbuild/android-arm64": "0.25.0",
|
||||||
"@esbuild/darwin-arm64": "0.18.20",
|
"@esbuild/android-x64": "0.25.0",
|
||||||
"@esbuild/darwin-x64": "0.18.20",
|
"@esbuild/darwin-arm64": "0.25.0",
|
||||||
"@esbuild/freebsd-arm64": "0.18.20",
|
"@esbuild/darwin-x64": "0.25.0",
|
||||||
"@esbuild/freebsd-x64": "0.18.20",
|
"@esbuild/freebsd-arm64": "0.25.0",
|
||||||
"@esbuild/linux-arm": "0.18.20",
|
"@esbuild/freebsd-x64": "0.25.0",
|
||||||
"@esbuild/linux-arm64": "0.18.20",
|
"@esbuild/linux-arm": "0.25.0",
|
||||||
"@esbuild/linux-ia32": "0.18.20",
|
"@esbuild/linux-arm64": "0.25.0",
|
||||||
"@esbuild/linux-loong64": "0.18.20",
|
"@esbuild/linux-ia32": "0.25.0",
|
||||||
"@esbuild/linux-mips64el": "0.18.20",
|
"@esbuild/linux-loong64": "0.25.0",
|
||||||
"@esbuild/linux-ppc64": "0.18.20",
|
"@esbuild/linux-mips64el": "0.25.0",
|
||||||
"@esbuild/linux-riscv64": "0.18.20",
|
"@esbuild/linux-ppc64": "0.25.0",
|
||||||
"@esbuild/linux-s390x": "0.18.20",
|
"@esbuild/linux-riscv64": "0.25.0",
|
||||||
"@esbuild/linux-x64": "0.18.20",
|
"@esbuild/linux-s390x": "0.25.0",
|
||||||
"@esbuild/netbsd-x64": "0.18.20",
|
"@esbuild/linux-x64": "0.25.0",
|
||||||
"@esbuild/openbsd-x64": "0.18.20",
|
"@esbuild/netbsd-arm64": "0.25.0",
|
||||||
"@esbuild/sunos-x64": "0.18.20",
|
"@esbuild/netbsd-x64": "0.25.0",
|
||||||
"@esbuild/win32-arm64": "0.18.20",
|
"@esbuild/openbsd-arm64": "0.25.0",
|
||||||
"@esbuild/win32-ia32": "0.18.20",
|
"@esbuild/openbsd-x64": "0.25.0",
|
||||||
"@esbuild/win32-x64": "0.18.20"
|
"@esbuild/sunos-x64": "0.25.0",
|
||||||
|
"@esbuild/win32-arm64": "0.25.0",
|
||||||
|
"@esbuild/win32-ia32": "0.25.0",
|
||||||
|
"@esbuild/win32-x64": "0.25.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild/node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"electron": "^30.1.2",
|
"electron": "^30.1.2",
|
||||||
"esbuild": "^0.18.11",
|
"esbuild": "^0.25.0",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-notice": "^1.0.0",
|
"eslint-plugin-notice": "^1.0.0",
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export const CopyToClipboard: React.FunctionComponent<CopyToClipboardProps> = ({
|
||||||
});
|
});
|
||||||
}, [value]);
|
}, [value]);
|
||||||
const iconElement = icon === 'check' ? icons.check() : icon === 'cross' ? icons.cross() : icons.copy();
|
const iconElement = icon === 'check' ? icons.check() : icon === 'cross' ? icons.cross() : icons.copy();
|
||||||
return <button className='copy-icon' aria-label='Copy to clipboard' onClick={handleCopy}>{iconElement}</button>;
|
return <button className='copy-icon' title='Copy to clipboard' aria-label='Copy to clipboard' onClick={handleCopy}>{iconElement}</button>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CopyToClipboardContainerProps = CopyToClipboardProps & {
|
type CopyToClipboardContainerProps = CopyToClipboardProps & {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
|
color: var(--color-fg-default);
|
||||||
}
|
}
|
||||||
|
|
||||||
.metadata-view {
|
.metadata-view {
|
||||||
|
|
@ -26,16 +27,46 @@
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.metadata-view .metadata-section {
|
||||||
|
margin: 8px 10px 8px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-view span:not(.copy-button-container),
|
||||||
|
.metadata-view a {
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-section {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-properties {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: normal;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-properties > div {
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.metadata-separator {
|
.metadata-separator {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
border-bottom: 1px solid var(--color-border-default);
|
border-bottom: 1px solid var(--color-border-default);
|
||||||
}
|
}
|
||||||
|
|
||||||
.metadata-view .copy-value-container {
|
|
||||||
margin-top: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.git-commit-info a {
|
.git-commit-info a {
|
||||||
color: var(--color-fg-default);
|
color: var(--color-fg-default);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copyable-property {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyable-property > span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,24 @@ import { linkifyText } from '@web/renderUtils';
|
||||||
|
|
||||||
type MetadataEntries = [string, unknown][];
|
type MetadataEntries = [string, unknown][];
|
||||||
|
|
||||||
export function filterMetadata(metadata: Metadata): MetadataEntries {
|
export const MetadataContext = React.createContext<MetadataEntries>([]);
|
||||||
// TODO: do not plumb actualWorkers through metadata.
|
|
||||||
return Object.entries(metadata).filter(([key]) => key !== 'actualWorkers');
|
export function MetadataProvider({ metadata, children }: React.PropsWithChildren<{ metadata: Metadata }>) {
|
||||||
|
const entries = React.useMemo(() => {
|
||||||
|
// TODO: do not plumb actualWorkers through metadata.
|
||||||
|
return Object.entries(metadata).filter(([key]) => key !== 'actualWorkers');
|
||||||
|
}, [metadata]);
|
||||||
|
|
||||||
|
return <MetadataContext.Provider value={entries}>{children}</MetadataContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMetadata() {
|
||||||
|
return React.useContext(MetadataContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGitCommitInfo() {
|
||||||
|
const metadataEntries = useMetadata();
|
||||||
|
return metadataEntries.find(([key]) => key === 'git.commit.info')?.[1] as GitCommitInfo | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ErrorBoundary extends React.Component<React.PropsWithChildren<{}>, { error: Error | null, errorInfo: React.ErrorInfo | null }> {
|
class ErrorBoundary extends React.Component<React.PropsWithChildren<{}>, { error: Error | null, errorInfo: React.ErrorInfo | null }> {
|
||||||
|
|
@ -57,12 +72,13 @@ class ErrorBoundary extends React.Component<React.PropsWithChildren<{}>, { error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MetadataView: React.FC<{ metadataEntries: MetadataEntries }> = ({ metadataEntries }) => {
|
export const MetadataView = () => {
|
||||||
return <ErrorBoundary><InnerMetadataView metadataEntries={metadataEntries}/></ErrorBoundary>;
|
return <ErrorBoundary><InnerMetadataView/></ErrorBoundary>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InnerMetadataView: React.FC<{ metadataEntries: MetadataEntries }> = ({ metadataEntries }) => {
|
const InnerMetadataView = () => {
|
||||||
const gitCommitInfo = metadataEntries.find(([key]) => key === 'git.commit.info')?.[1] as GitCommitInfo | undefined;
|
const metadataEntries = useMetadata();
|
||||||
|
const gitCommitInfo = useGitCommitInfo();
|
||||||
const entries = metadataEntries.filter(([key]) => key !== 'git.commit.info');
|
const entries = metadataEntries.filter(([key]) => key !== 'git.commit.info');
|
||||||
if (!gitCommitInfo && !entries.length)
|
if (!gitCommitInfo && !entries.length)
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -71,30 +87,43 @@ const InnerMetadataView: React.FC<{ metadataEntries: MetadataEntries }> = ({ met
|
||||||
<GitCommitInfoView info={gitCommitInfo}/>
|
<GitCommitInfoView info={gitCommitInfo}/>
|
||||||
{entries.length > 0 && <div className='metadata-separator' />}
|
{entries.length > 0 && <div className='metadata-separator' />}
|
||||||
</>}
|
</>}
|
||||||
{entries.map(([key, value]) => {
|
<div className='metadata-section metadata-properties'>
|
||||||
const valueString = typeof value !== 'object' || value === null || value === undefined ? String(value) : JSON.stringify(value);
|
{entries.map(([propertyName, value]) => {
|
||||||
const trimmedValue = valueString.length > 1000 ? valueString.slice(0, 1000) + '\u2026' : valueString;
|
const valueString = typeof value !== 'object' || value === null || value === undefined ? String(value) : JSON.stringify(value);
|
||||||
return <div className='m-1 ml-5' key={key}>
|
const trimmedValue = valueString.length > 1000 ? valueString.slice(0, 1000) + '\u2026' : valueString;
|
||||||
<span style={{ fontWeight: 'bold' }} title={key}>{key}</span>
|
return (
|
||||||
{valueString && <CopyToClipboardContainer value={valueString}>: <span title={trimmedValue}>{linkifyText(trimmedValue)}</span></CopyToClipboardContainer>}
|
<div key={propertyName} className='copyable-property'>
|
||||||
</div>;
|
<CopyToClipboardContainer value={valueString}>
|
||||||
})}
|
<span style={{ fontWeight: 'bold' }} title={propertyName}>{propertyName}</span>
|
||||||
|
: <span title={trimmedValue}>{linkifyText(trimmedValue)}</span>
|
||||||
|
</CopyToClipboardContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => {
|
const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => {
|
||||||
const email = info['revision.email'] ? ` <${info['revision.email']}>` : '';
|
const email = info['revision.email'] ? ` <${info['revision.email']}>` : '';
|
||||||
const author = `${info['revision.author'] || ''}${email}`;
|
const author = `${info['revision.author'] || ''}${email}`;
|
||||||
|
const subject = info['revision.subject'] || '';
|
||||||
const shortTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(info['revision.timestamp']);
|
const shortTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(info['revision.timestamp']);
|
||||||
const longTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long' }).format(info['revision.timestamp']);
|
const longTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long' }).format(info['revision.timestamp']);
|
||||||
return <div className='hbox pl-4 pr-2 git-commit-info' style={{ alignItems: 'center' }}>
|
return <div className='hbox git-commit-info metadata-section'>
|
||||||
<div className='vbox'>
|
<div className='vbox metadata-properties'>
|
||||||
<a className='m-2' href={info['revision.link']} target='_blank' rel='noopener noreferrer'>
|
<div>
|
||||||
<span title={info['revision.subject'] || ''}>{info['revision.subject'] || ''}</span>
|
{info['revision.link'] ? (
|
||||||
</a>
|
<a href={info['revision.link']} target='_blank' rel='noopener noreferrer' title={subject}>
|
||||||
<div className='hbox m-2 mt-1'>
|
{subject}
|
||||||
<div className='mr-1'>{author}</div>
|
</a>
|
||||||
<div title={longTimestamp}> on {shortTimestamp}</div>
|
) : <span title={subject}>
|
||||||
|
{subject}
|
||||||
|
</span>}
|
||||||
|
</div>
|
||||||
|
<div className='hbox'>
|
||||||
|
<span className='mr-1'>{author}</span>
|
||||||
|
<span title={longTimestamp}> on {shortTimestamp}</span>
|
||||||
{info['ci.link'] && (
|
{info['ci.link'] && (
|
||||||
<>
|
<>
|
||||||
<span className='mx-2'>·</span>
|
<span className='mx-2'>·</span>
|
||||||
|
|
@ -109,9 +138,10 @@ const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!!info['revision.link'] && <a href={info['revision.link']} target='_blank' rel='noopener noreferrer'>
|
{!!info['revision.link'] ? (
|
||||||
<span title='View commit details'>{info['revision.id']?.slice(0, 7) || 'unknown'}</span>
|
<a href={info['revision.link']} target='_blank' rel='noopener noreferrer' title='View commit details'>
|
||||||
</a>}
|
{info['revision.id']?.slice(0, 7) || 'unknown'}
|
||||||
{!info['revision.link'] && !!info['revision.id'] && <span>{info['revision.id'].slice(0, 7)}</span>}
|
</a>
|
||||||
|
) : !!info['revision.id'] && <span>{info['revision.id'].slice(0, 7)}</span>}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import './reportView.css';
|
||||||
import { TestCaseView } from './testCaseView';
|
import { TestCaseView } from './testCaseView';
|
||||||
import { TestFilesHeader, TestFilesView } from './testFilesView';
|
import { TestFilesHeader, TestFilesView } from './testFilesView';
|
||||||
import './theme.css';
|
import './theme.css';
|
||||||
|
import { MetadataProvider } from './metadataView';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
|
@ -72,7 +73,7 @@ export const ReportView: React.FC<{
|
||||||
return result;
|
return result;
|
||||||
}, [report, filter]);
|
}, [report, filter]);
|
||||||
|
|
||||||
return <div className='htmlreport vbox px-4 pb-4'>
|
return <MetadataProvider metadata={report?.json().metadata ?? {}}><div className='htmlreport vbox px-4 pb-4'>
|
||||||
<main>
|
<main>
|
||||||
{report?.json() && <HeaderView stats={report.json().stats} filterText={filterText} setFilterText={setFilterText}></HeaderView>}
|
{report?.json() && <HeaderView stats={report.json().stats} filterText={filterText} setFilterText={setFilterText}></HeaderView>}
|
||||||
<Route predicate={testFilesRoutePredicate}>
|
<Route predicate={testFilesRoutePredicate}>
|
||||||
|
|
@ -88,7 +89,7 @@ export const ReportView: React.FC<{
|
||||||
{!!report && <TestCaseViewLoader report={report} tests={filteredTests.tests} testIdToFileIdMap={testIdToFileIdMap} />}
|
{!!report && <TestCaseViewLoader report={report} tests={filteredTests.tests} testIdToFileIdMap={testIdToFileIdMap} />}
|
||||||
</Route>
|
</Route>
|
||||||
</main>
|
</main>
|
||||||
</div>;
|
</div></MetadataProvider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TestCaseViewLoader: React.FC<{
|
const TestCaseViewLoader: React.FC<{
|
||||||
|
|
|
||||||
|
|
@ -16,18 +16,47 @@
|
||||||
|
|
||||||
@import '@web/third_party/vscode/colors.css';
|
@import '@web/third_party/vscode/colors.css';
|
||||||
|
|
||||||
.test-error-view {
|
.test-error-container {
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
flex: none;
|
flex: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: var(--color-canvas-subtle);
|
background-color: var(--color-canvas-subtle);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 16px;
|
|
||||||
line-height: initial;
|
line-height: initial;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.test-error-view {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.test-error-text {
|
.test-error-text {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prompt-button {
|
||||||
|
flex: none;
|
||||||
|
height: 24px;
|
||||||
|
width: 80px;
|
||||||
|
border: 1px solid var(--color-btn-border);
|
||||||
|
outline: none;
|
||||||
|
color: var(--color-btn-text);
|
||||||
|
background: var(--color-btn-bg);
|
||||||
|
padding: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-button svg {
|
||||||
|
color: var(--color-fg-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-button:not(:disabled):hover {
|
||||||
|
border-color: var(--color-btn-hover-border);
|
||||||
|
background-color: var(--color-btn-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,57 @@
|
||||||
import { ansi2html } from '@web/ansi2html';
|
import { ansi2html } from '@web/ansi2html';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import './testErrorView.css';
|
import './testErrorView.css';
|
||||||
|
import * as icons from './icons';
|
||||||
import type { ImageDiff } from '@web/shared/imageDiffView';
|
import type { ImageDiff } from '@web/shared/imageDiffView';
|
||||||
import { ImageDiffView } from '@web/shared/imageDiffView';
|
import { ImageDiffView } from '@web/shared/imageDiffView';
|
||||||
|
import type { TestResult } from './types';
|
||||||
|
import { fixTestPrompt } from '@web/components/prompts';
|
||||||
|
import { useGitCommitInfo } from './metadataView';
|
||||||
|
|
||||||
export const TestErrorView: React.FC<{
|
export const TestErrorView: React.FC<{ error: string; testId?: string; result?: TestResult }> = ({ error, testId, result }) => {
|
||||||
|
return (
|
||||||
|
<CodeSnippet code={error} testId={testId}>
|
||||||
|
<div style={{ float: 'right', padding: '5px' }}>
|
||||||
|
<PromptButton error={error} result={result} />
|
||||||
|
</div>
|
||||||
|
</CodeSnippet>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CodeSnippet = ({ code, children, testId }: React.PropsWithChildren<{ code: string; testId?: string; }>) => {
|
||||||
|
const html = React.useMemo(() => ansiErrorToHtml(code), [code]);
|
||||||
|
return (
|
||||||
|
<div className='test-error-container test-error-text' data-testid={testId}>
|
||||||
|
{children}
|
||||||
|
<div className='test-error-view' dangerouslySetInnerHTML={{ __html: html || '' }}></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PromptButton: React.FC<{
|
||||||
error: string;
|
error: string;
|
||||||
testId?: string;
|
result?: TestResult;
|
||||||
}> = ({ error, testId }) => {
|
}> = ({ error, result }) => {
|
||||||
const html = React.useMemo(() => ansiErrorToHtml(error), [error]);
|
const gitCommitInfo = useGitCommitInfo();
|
||||||
return <div className='test-error-view test-error-text' data-testid={testId} dangerouslySetInnerHTML={{ __html: html || '' }}></div>;
|
const prompt = React.useMemo(() => fixTestPrompt(
|
||||||
|
error,
|
||||||
|
gitCommitInfo?.['pull.diff'] ?? gitCommitInfo?.['revision.diff'],
|
||||||
|
result?.attachments.find(a => a.name === 'pageSnapshot')?.body
|
||||||
|
), [gitCommitInfo, result, error]);
|
||||||
|
|
||||||
|
const [copied, setCopied] = React.useState(false);
|
||||||
|
|
||||||
|
return <button
|
||||||
|
className='prompt-button'
|
||||||
|
onClick={async () => {
|
||||||
|
await navigator.clipboard.writeText(prompt);
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setCopied(false);
|
||||||
|
}, 3000);
|
||||||
|
}}>
|
||||||
|
{copied ? <span className='prompt-button-copied'>Copied <icons.copy/></span> : 'Fix with AI'}
|
||||||
|
</button>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TestScreenshotErrorView: React.FC<{
|
export const TestScreenshotErrorView: React.FC<{
|
||||||
|
|
|
||||||
|
|
@ -69,4 +69,11 @@
|
||||||
|
|
||||||
.test-file-test-status-icon {
|
.test-file-test-status-icon {
|
||||||
flex: none;
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-file-header-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: var(--color-fg-subtle);
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ import { msToString } from './utils';
|
||||||
import { AutoChip } from './chip';
|
import { AutoChip } from './chip';
|
||||||
import { TestErrorView } from './testErrorView';
|
import { TestErrorView } from './testErrorView';
|
||||||
import * as icons from './icons';
|
import * as icons from './icons';
|
||||||
import { filterMetadata, MetadataView } from './metadataView';
|
import { MetadataView, useMetadata } from './metadataView';
|
||||||
|
|
||||||
export const TestFilesView: React.FC<{
|
export const TestFilesView: React.FC<{
|
||||||
tests: TestFileSummary[],
|
tests: TestFileSummary[],
|
||||||
|
|
@ -67,21 +67,23 @@ export const TestFilesHeader: React.FC<{
|
||||||
metadataVisible: boolean,
|
metadataVisible: boolean,
|
||||||
toggleMetadataVisible: () => void,
|
toggleMetadataVisible: () => void,
|
||||||
}> = ({ report, filteredStats, metadataVisible, toggleMetadataVisible }) => {
|
}> = ({ report, filteredStats, metadataVisible, toggleMetadataVisible }) => {
|
||||||
|
const metadataEntries = useMetadata();
|
||||||
if (!report)
|
if (!report)
|
||||||
return;
|
return null;
|
||||||
const metadataEntries = filterMetadata(report.metadata || {});
|
|
||||||
return <>
|
return <>
|
||||||
<div className='mx-1' style={{ display: 'flex', marginTop: 10 }}>
|
<div className='mx-1' style={{ display: 'flex', marginTop: 10 }}>
|
||||||
{metadataEntries.length > 0 && <div className='metadata-toggle' role='button' onClick={toggleMetadataVisible} title={metadataVisible ? 'Hide metadata' : 'Show metadata'}>
|
<div className='test-file-header-info'>
|
||||||
{metadataVisible ? icons.downArrow() : icons.rightArrow()}Metadata
|
{metadataEntries.length > 0 && <div className='metadata-toggle' role='button' onClick={toggleMetadataVisible} title={metadataVisible ? 'Hide metadata' : 'Show metadata'}>
|
||||||
</div>}
|
{metadataVisible ? icons.downArrow() : icons.rightArrow()}Metadata
|
||||||
{report.projectNames.length === 1 && !!report.projectNames[0] && <div data-testid='project-name' style={{ color: 'var(--color-fg-subtle)' }}>Project: {report.projectNames[0]}</div>}
|
</div>}
|
||||||
{filteredStats && <div data-testid='filtered-tests-count' style={{ color: 'var(--color-fg-subtle)', padding: '0 10px' }}>Filtered: {filteredStats.total} {!!filteredStats.total && ('(' + msToString(filteredStats.duration) + ')')}</div>}
|
{report.projectNames.length === 1 && !!report.projectNames[0] && <div data-testid='project-name'>Project: {report.projectNames[0]}</div>}
|
||||||
|
{filteredStats && <div data-testid='filtered-tests-count'>Filtered: {filteredStats.total} {!!filteredStats.total && ('(' + msToString(filteredStats.duration) + ')')}</div>}
|
||||||
|
</div>
|
||||||
<div style={{ flex: 'auto' }}></div>
|
<div style={{ flex: 'auto' }}></div>
|
||||||
<div data-testid='overall-time' style={{ color: 'var(--color-fg-subtle)', marginRight: '10px' }}>{report ? new Date(report.startTime).toLocaleString() : ''}</div>
|
<div data-testid='overall-time' style={{ color: 'var(--color-fg-subtle)', marginRight: '10px' }}>{report ? new Date(report.startTime).toLocaleString() : ''}</div>
|
||||||
<div data-testid='overall-duration' style={{ color: 'var(--color-fg-subtle)' }}>Total time: {msToString(report.duration ?? 0)}</div>
|
<div data-testid='overall-duration' style={{ color: 'var(--color-fg-subtle)' }}>Total time: {msToString(report.duration ?? 0)}</div>
|
||||||
</div>
|
</div>
|
||||||
{metadataVisible && <MetadataView metadataEntries={metadataEntries}/>}
|
{metadataVisible && <MetadataView/>}
|
||||||
{!!report.errors.length && <AutoChip header='Errors' dataTestId='report-errors'>
|
{!!report.errors.length && <AutoChip header='Errors' dataTestId='report-errors'>
|
||||||
{report.errors.map((error, index) => <TestErrorView key={'test-report-error-message-' + index} error={error}></TestErrorView>)}
|
{report.errors.map((error, index) => <TestErrorView key={'test-report-error-message-' + index} error={error}></TestErrorView>)}
|
||||||
</AutoChip>}
|
</AutoChip>}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import { Anchor, AttachmentLink, generateTraceUrl, testResultHref } from './link
|
||||||
import { statusIcon } from './statusIcon';
|
import { statusIcon } from './statusIcon';
|
||||||
import type { ImageDiff } from '@web/shared/imageDiffView';
|
import type { ImageDiff } from '@web/shared/imageDiffView';
|
||||||
import { ImageDiffView } from '@web/shared/imageDiffView';
|
import { ImageDiffView } from '@web/shared/imageDiffView';
|
||||||
import { TestErrorView, TestScreenshotErrorView } from './testErrorView';
|
import { CodeSnippet, TestErrorView, TestScreenshotErrorView } from './testErrorView';
|
||||||
import * as icons from './icons';
|
import * as icons from './icons';
|
||||||
import './testResultView.css';
|
import './testResultView.css';
|
||||||
|
|
||||||
|
|
@ -90,7 +90,7 @@ export const TestResultView: React.FC<{
|
||||||
{errors.map((error, index) => {
|
{errors.map((error, index) => {
|
||||||
if (error.type === 'screenshot')
|
if (error.type === 'screenshot')
|
||||||
return <TestScreenshotErrorView key={'test-result-error-message-' + index} errorPrefix={error.errorPrefix} diff={error.diff!} errorSuffix={error.errorSuffix}></TestScreenshotErrorView>;
|
return <TestScreenshotErrorView key={'test-result-error-message-' + index} errorPrefix={error.errorPrefix} diff={error.diff!} errorSuffix={error.errorSuffix}></TestScreenshotErrorView>;
|
||||||
return <TestErrorView key={'test-result-error-message-' + index} error={error.error!}></TestErrorView>;
|
return <TestErrorView key={'test-result-error-message-' + index} error={error.error!} result={result}></TestErrorView>;
|
||||||
})}
|
})}
|
||||||
</AutoChip>}
|
</AutoChip>}
|
||||||
{!!result.steps.length && <AutoChip header='Test Steps'>
|
{!!result.steps.length && <AutoChip header='Test Steps'>
|
||||||
|
|
@ -182,7 +182,7 @@ const StepTreeItem: React.FC<{
|
||||||
{step.count > 1 && <> ✕ <span className='test-result-counter'>{step.count}</span></>}
|
{step.count > 1 && <> ✕ <span className='test-result-counter'>{step.count}</span></>}
|
||||||
{step.location && <span className='test-result-path'>— {step.location.file}:{step.location.line}</span>}
|
{step.location && <span className='test-result-path'>— {step.location.file}:{step.location.line}</span>}
|
||||||
</span>} loadChildren={step.steps.length || step.snippet ? () => {
|
</span>} loadChildren={step.steps.length || step.snippet ? () => {
|
||||||
const snippet = step.snippet ? [<TestErrorView testId='test-snippet' key='line' error={step.snippet}/>] : [];
|
const snippet = step.snippet ? [<CodeSnippet testId='test-snippet' key='line' code={step.snippet} />] : [];
|
||||||
const steps = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1} result={result} test={test} />);
|
const steps = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1} result={result} test={test} />);
|
||||||
return snippet.concat(steps);
|
return snippet.concat(steps);
|
||||||
} : undefined} depth={depth}/>;
|
} : undefined} depth={depth}/>;
|
||||||
|
|
|
||||||
|
|
@ -3,33 +3,33 @@
|
||||||
"browsers": [
|
"browsers": [
|
||||||
{
|
{
|
||||||
"name": "chromium",
|
"name": "chromium",
|
||||||
"revision": "1157",
|
"revision": "1158",
|
||||||
"installByDefault": true,
|
"installByDefault": true,
|
||||||
"browserVersion": "133.0.6943.35"
|
"browserVersion": "134.0.6998.3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "chromium-headless-shell",
|
"name": "chromium-headless-shell",
|
||||||
"revision": "1157",
|
"revision": "1158",
|
||||||
"installByDefault": true,
|
"installByDefault": true,
|
||||||
"browserVersion": "133.0.6943.35"
|
"browserVersion": "134.0.6998.3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "chromium-tip-of-tree",
|
"name": "chromium-tip-of-tree",
|
||||||
"revision": "1300",
|
"revision": "1302",
|
||||||
"installByDefault": false,
|
"installByDefault": false,
|
||||||
"browserVersion": "134.0.6998.0"
|
"browserVersion": "135.0.7011.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "chromium-tip-of-tree-headless-shell",
|
"name": "chromium-tip-of-tree-headless-shell",
|
||||||
"revision": "1300",
|
"revision": "1302",
|
||||||
"installByDefault": false,
|
"installByDefault": false,
|
||||||
"browserVersion": "134.0.6998.0"
|
"browserVersion": "135.0.7011.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "firefox",
|
"name": "firefox",
|
||||||
"revision": "1474",
|
"revision": "1475",
|
||||||
"installByDefault": true,
|
"installByDefault": true,
|
||||||
"browserVersion": "134.0"
|
"browserVersion": "135.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "firefox-beta",
|
"name": "firefox-beta",
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "webkit",
|
"name": "webkit",
|
||||||
"revision": "2130",
|
"revision": "2132",
|
||||||
"installByDefault": true,
|
"installByDefault": true,
|
||||||
"revisionOverrides": {
|
"revisionOverrides": {
|
||||||
"debian11-x64": "2105",
|
"debian11-x64": "2105",
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,11 @@
|
||||||
[inProcessFactory.ts]
|
[inProcessFactory.ts]
|
||||||
**
|
**
|
||||||
|
|
||||||
|
[inprocess.ts]
|
||||||
|
utils/
|
||||||
|
|
||||||
[outofprocess.ts]
|
[outofprocess.ts]
|
||||||
client/
|
client/
|
||||||
protocol/
|
protocol/
|
||||||
utils/
|
utils/
|
||||||
|
common/
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
../common
|
../common
|
||||||
../debug/injected
|
../debug/injected
|
||||||
../generated/
|
../generated/
|
||||||
|
../server/
|
||||||
../server/injected/
|
../server/injected/
|
||||||
../server/trace
|
../server/trace
|
||||||
../utils
|
../utils
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import * as playwright from '../..';
|
||||||
import { PipeTransport } from '../protocol/transport';
|
import { PipeTransport } from '../protocol/transport';
|
||||||
import { PlaywrightServer } from '../remote/playwrightServer';
|
import { PlaywrightServer } from '../remote/playwrightServer';
|
||||||
import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from '../server';
|
import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from '../server';
|
||||||
import { gracefullyProcessExitDoNotHang } from '../utils/processLauncher';
|
import { gracefullyProcessExitDoNotHang } from '../server/processLauncher';
|
||||||
|
|
||||||
import type { BrowserType } from '../client/browserType';
|
import type { BrowserType } from '../client/browserType';
|
||||||
import type { LaunchServerOptions } from '../client/types';
|
import type { LaunchServerOptions } from '../client/types';
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,11 @@ import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import * as playwright from '../..';
|
import * as playwright from '../..';
|
||||||
import { registry, writeDockerVersion } from '../server';
|
|
||||||
import { launchBrowserServer, printApiJson, runDriver, runServer } from './driver';
|
import { launchBrowserServer, printApiJson, runDriver, runServer } from './driver';
|
||||||
import { isTargetClosedError } from '../client/errors';
|
import { isTargetClosedError } from '../client/errors';
|
||||||
|
import { gracefullyProcessExitDoNotHang, registry, writeDockerVersion } from '../server';
|
||||||
import { runTraceInBrowser, runTraceViewerApp } from '../server/trace/viewer/traceViewer';
|
import { runTraceInBrowser, runTraceViewerApp } from '../server/trace/viewer/traceViewer';
|
||||||
import { assert, getPackageManagerExecCommand, gracefullyProcessExitDoNotHang, isLikelyNpxGlobal, wrapInASCIIBox } from '../utils';
|
import { assert, getPackageManagerExecCommand, isLikelyNpxGlobal, wrapInASCIIBox } from '../utils';
|
||||||
import { dotenv, program } from '../utilsBundle';
|
import { dotenv, program } from '../utilsBundle';
|
||||||
|
|
||||||
import type { Browser } from '../client/browser';
|
import type { Browser } from '../client/browser';
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@
|
||||||
|
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
import { getPackageManager, gracefullyProcessExitDoNotHang } from '../utils';
|
import { gracefullyProcessExitDoNotHang } from '../server';
|
||||||
|
import { getPackageManager } from '../utils';
|
||||||
import { program } from './program';
|
import { program } from './program';
|
||||||
export { program } from './program';
|
export { program } from './program';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,22 +15,22 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
import { isRegExp, isString, monotonicTime } from '../utils';
|
|
||||||
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { Connection } from './connection';
|
|
||||||
import { TargetClosedError, isTargetClosedError } from './errors';
|
import { TargetClosedError, isTargetClosedError } from './errors';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
import { TimeoutSettings } from '../common/timeoutSettings';
|
import { TimeoutSettings } from '../common/timeoutSettings';
|
||||||
|
import { isRegExp, isString } from '../utils/rtti';
|
||||||
|
import { monotonicTime } from '../utils/time';
|
||||||
import { raceAgainstDeadline } from '../utils/timeoutRunner';
|
import { raceAgainstDeadline } from '../utils/timeoutRunner';
|
||||||
|
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type { AndroidServerLauncherImpl } from '../androidServerImpl';
|
import type { AndroidServerLauncherImpl } from '../androidServerImpl';
|
||||||
|
import type { Platform } from '../utils/platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
type Direction = 'down' | 'up' | 'left' | 'right';
|
type Direction = 'down' | 'up' | 'left' | 'right';
|
||||||
|
|
@ -71,45 +71,28 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
|
||||||
const headers = { 'x-playwright-browser': 'android', ...options.headers };
|
const headers = { 'x-playwright-browser': 'android', ...options.headers };
|
||||||
const localUtils = this._connection.localUtils();
|
const localUtils = this._connection.localUtils();
|
||||||
const connectParams: channels.LocalUtilsConnectParams = { wsEndpoint, headers, slowMo: options.slowMo, timeout: options.timeout };
|
const connectParams: channels.LocalUtilsConnectParams = { wsEndpoint, headers, slowMo: options.slowMo, timeout: options.timeout };
|
||||||
const { pipe } = await localUtils._channel.connect(connectParams);
|
const connection = await localUtils.connect(connectParams);
|
||||||
const closePipe = () => pipe.close().catch(() => {});
|
|
||||||
const connection = new Connection(localUtils, this._instrumentation);
|
|
||||||
connection.markAsRemote();
|
|
||||||
connection.on('close', closePipe);
|
|
||||||
|
|
||||||
let device: AndroidDevice;
|
let device: AndroidDevice;
|
||||||
let closeError: string | undefined;
|
connection.on('close', () => {
|
||||||
const onPipeClosed = () => {
|
|
||||||
device?._didClose();
|
device?._didClose();
|
||||||
connection.close(closeError);
|
|
||||||
};
|
|
||||||
pipe.on('closed', onPipeClosed);
|
|
||||||
connection.onmessage = message => pipe.send({ message }).catch(onPipeClosed);
|
|
||||||
|
|
||||||
pipe.on('message', ({ message }) => {
|
|
||||||
try {
|
|
||||||
connection!.dispatch(message);
|
|
||||||
} catch (e) {
|
|
||||||
closeError = String(e);
|
|
||||||
closePipe();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await raceAgainstDeadline(async () => {
|
const result = await raceAgainstDeadline(async () => {
|
||||||
const playwright = await connection!.initializePlaywright();
|
const playwright = await connection!.initializePlaywright();
|
||||||
if (!playwright._initializer.preConnectedAndroidDevice) {
|
if (!playwright._initializer.preConnectedAndroidDevice) {
|
||||||
closePipe();
|
connection.close();
|
||||||
throw new Error('Malformed endpoint. Did you use Android.launchServer method?');
|
throw new Error('Malformed endpoint. Did you use Android.launchServer method?');
|
||||||
}
|
}
|
||||||
device = AndroidDevice.from(playwright._initializer.preConnectedAndroidDevice!);
|
device = AndroidDevice.from(playwright._initializer.preConnectedAndroidDevice!);
|
||||||
device._shouldCloseConnectionOnClose = true;
|
device._shouldCloseConnectionOnClose = true;
|
||||||
device.on(Events.AndroidDevice.Close, closePipe);
|
device.on(Events.AndroidDevice.Close, () => connection.close());
|
||||||
return device;
|
return device;
|
||||||
}, deadline);
|
}, deadline);
|
||||||
if (!result.timedOut) {
|
if (!result.timedOut) {
|
||||||
return result.result;
|
return result.result;
|
||||||
} else {
|
} else {
|
||||||
closePipe();
|
connection.close();
|
||||||
throw new Error(`Timeout ${options.timeout}ms exceeded`);
|
throw new Error(`Timeout ${options.timeout}ms exceeded`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -232,7 +215,7 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
|
||||||
async screenshot(options: { path?: string } = {}): Promise<Buffer> {
|
async screenshot(options: { path?: string } = {}): Promise<Buffer> {
|
||||||
const { binary } = await this._channel.screenshot();
|
const { binary } = await this._channel.screenshot();
|
||||||
if (options.path)
|
if (options.path)
|
||||||
await fs.promises.writeFile(options.path, binary);
|
await this._platform.fs().promises.writeFile(options.path, binary);
|
||||||
return binary;
|
return binary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -267,15 +250,15 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
|
||||||
}
|
}
|
||||||
|
|
||||||
async installApk(file: string | Buffer, options?: { args: string[] }): Promise<void> {
|
async installApk(file: string | Buffer, options?: { args: string[] }): Promise<void> {
|
||||||
await this._channel.installApk({ file: await loadFile(file), args: options && options.args });
|
await this._channel.installApk({ file: await loadFile(this._platform, file), args: options && options.args });
|
||||||
}
|
}
|
||||||
|
|
||||||
async push(file: string | Buffer, path: string, options?: { mode: number }): Promise<void> {
|
async push(file: string | Buffer, path: string, options?: { mode: number }): Promise<void> {
|
||||||
await this._channel.push({ file: await loadFile(file), path, mode: options ? options.mode : undefined });
|
await this._channel.push({ file: await loadFile(this._platform, file), path, mode: options ? options.mode : undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise<BrowserContext> {
|
async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise<BrowserContext> {
|
||||||
const contextOptions = await prepareBrowserContextParams(options);
|
const contextOptions = await prepareBrowserContextParams(this._platform, options);
|
||||||
const result = await this._channel.launchBrowser(contextOptions);
|
const result = await this._channel.launchBrowser(contextOptions);
|
||||||
const context = BrowserContext.from(result.context) as BrowserContext;
|
const context = BrowserContext.from(result.context) as BrowserContext;
|
||||||
context._setOptions(contextOptions, {});
|
context._setOptions(contextOptions, {});
|
||||||
|
|
@ -321,9 +304,9 @@ export class AndroidSocket extends ChannelOwner<channels.AndroidSocketChannel> i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadFile(file: string | Buffer): Promise<Buffer> {
|
async function loadFile(platform: Platform, file: string | Buffer): Promise<Buffer> {
|
||||||
if (isString(file))
|
if (isString(file))
|
||||||
return await fs.promises.readFile(file);
|
return await platform.fs().promises.readFile(file);
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { Stream } from './stream';
|
import { Stream } from './stream';
|
||||||
import { mkdirIfNeeded } from '../utils/fileUtils';
|
import { mkdirIfNeeded } from '../utils/fileUtils';
|
||||||
|
|
@ -42,9 +40,9 @@ export class Artifact extends ChannelOwner<channels.ArtifactChannel> {
|
||||||
|
|
||||||
const result = await this._channel.saveAsStream();
|
const result = await this._channel.saveAsStream();
|
||||||
const stream = Stream.from(result.stream);
|
const stream = Stream.from(result.stream);
|
||||||
await mkdirIfNeeded(path);
|
await mkdirIfNeeded(this._platform, path);
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
stream.stream().pipe(fs.createWriteStream(path))
|
stream.stream().pipe(this._platform.fs().createWriteStream(path))
|
||||||
.on('finish' as any, resolve)
|
.on('finish' as any, resolve)
|
||||||
.on('error' as any, reject);
|
.on('error' as any, reject);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -14,19 +14,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
import { Artifact } from './artifact';
|
import { Artifact } from './artifact';
|
||||||
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
||||||
import { CDPSession } from './cdpSession';
|
import { CDPSession } from './cdpSession';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { isTargetClosedError } from './errors';
|
import { isTargetClosedError } from './errors';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { mkdirIfNeeded } from '../utils';
|
import { mkdirIfNeeded } from '../utils/fileUtils';
|
||||||
|
|
||||||
import type { BrowserType } from './browserType';
|
import type { BrowserType } from './browserType';
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
import type { BrowserContextOptions, HeadersArray, LaunchOptions } from './types';
|
import type { BrowserContextOptions, LaunchOptions } from './types';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
|
|
@ -39,9 +37,6 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
||||||
_options: LaunchOptions = {};
|
_options: LaunchOptions = {};
|
||||||
readonly _name: string;
|
readonly _name: string;
|
||||||
private _path: string | undefined;
|
private _path: string | undefined;
|
||||||
|
|
||||||
// Used from @playwright/test fixtures.
|
|
||||||
_connectHeaders?: HeadersArray;
|
|
||||||
_closeReason: string | undefined;
|
_closeReason: string | undefined;
|
||||||
|
|
||||||
static from(browser: channels.BrowserChannel): Browser {
|
static from(browser: channels.BrowserChannel): Browser {
|
||||||
|
|
@ -83,7 +78,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
||||||
|
|
||||||
async _innerNewContext(options: BrowserContextOptions = {}, forReuse: boolean): Promise<BrowserContext> {
|
async _innerNewContext(options: BrowserContextOptions = {}, forReuse: boolean): Promise<BrowserContext> {
|
||||||
options = { ...this._browserType._playwright._defaultContextOptions, ...options };
|
options = { ...this._browserType._playwright._defaultContextOptions, ...options };
|
||||||
const contextOptions = await prepareBrowserContextParams(options);
|
const contextOptions = await prepareBrowserContextParams(this._platform, options);
|
||||||
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
|
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
|
||||||
const context = BrowserContext.from(response.context);
|
const context = BrowserContext.from(response.context);
|
||||||
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);
|
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);
|
||||||
|
|
@ -126,8 +121,8 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
||||||
const buffer = await artifact.readIntoBuffer();
|
const buffer = await artifact.readIntoBuffer();
|
||||||
await artifact.delete();
|
await artifact.delete();
|
||||||
if (this._path) {
|
if (this._path) {
|
||||||
await mkdirIfNeeded(this._path);
|
await mkdirIfNeeded(this._platform, this._path);
|
||||||
await fs.promises.writeFile(this._path, buffer);
|
await this._platform.fs().promises.writeFile(this._path, buffer);
|
||||||
this._path = undefined;
|
this._path = undefined;
|
||||||
}
|
}
|
||||||
return buffer;
|
return buffer;
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
import { Artifact } from './artifact';
|
import { Artifact } from './artifact';
|
||||||
import { Browser } from './browser';
|
import { Browser } from './browser';
|
||||||
import { CDPSession } from './cdpSession';
|
import { CDPSession } from './cdpSession';
|
||||||
|
|
@ -38,14 +35,18 @@ import { Waiter } from './waiter';
|
||||||
import { WebError } from './webError';
|
import { WebError } from './webError';
|
||||||
import { Worker } from './worker';
|
import { Worker } from './worker';
|
||||||
import { TimeoutSettings } from '../common/timeoutSettings';
|
import { TimeoutSettings } from '../common/timeoutSettings';
|
||||||
import { headersObjectToArray, isRegExp, isString, mkdirIfNeeded, urlMatchesEqual } from '../utils';
|
import { mkdirIfNeeded } from '../utils/fileUtils';
|
||||||
|
import { headersObjectToArray } from '../utils/headers';
|
||||||
|
import { urlMatchesEqual } from '../utils/isomorphic/urlMatch';
|
||||||
|
import { isRegExp, isString } from '../utils/rtti';
|
||||||
import { rewriteErrorMessage } from '../utils/stackTrace';
|
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||||
|
|
||||||
import type { BrowserType } from './browserType';
|
import type { BrowserType } from './browserType';
|
||||||
import type { BrowserContextOptions, Headers, LaunchOptions, StorageState, WaitForEventOptions } from './types';
|
import type { BrowserContextOptions, Headers, LaunchOptions, StorageState, WaitForEventOptions } from './types';
|
||||||
import type * as structs from '../../types/structs';
|
import type * as structs from '../../types/structs';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type { URLMatch } from '../utils';
|
import type { URLMatch } from '../utils/isomorphic/urlMatch';
|
||||||
|
import type { Platform } from '../utils/platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext {
|
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext {
|
||||||
|
|
@ -107,7 +108,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
this.emit(Events.BrowserContext.ServiceWorker, serviceWorker);
|
this.emit(Events.BrowserContext.ServiceWorker, serviceWorker);
|
||||||
});
|
});
|
||||||
this._channel.on('console', event => {
|
this._channel.on('console', event => {
|
||||||
const consoleMessage = new ConsoleMessage(event);
|
const consoleMessage = new ConsoleMessage(this._platform, event);
|
||||||
this.emit(Events.BrowserContext.Console, consoleMessage);
|
this.emit(Events.BrowserContext.Console, consoleMessage);
|
||||||
const page = consoleMessage.page();
|
const page = consoleMessage.page();
|
||||||
if (page)
|
if (page)
|
||||||
|
|
@ -321,7 +322,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
}
|
}
|
||||||
|
|
||||||
async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise<void> {
|
async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise<void> {
|
||||||
const source = await evaluationScript(script, arg);
|
const source = await evaluationScript(this._platform, script, arg);
|
||||||
await this._channel.addInitScript({ source });
|
await this._channel.addInitScript({ source });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -431,8 +432,8 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
async storageState(options: { path?: string, indexedDB?: boolean } = {}): Promise<StorageState> {
|
async storageState(options: { path?: string, indexedDB?: boolean } = {}): Promise<StorageState> {
|
||||||
const state = await this._channel.storageState({ indexedDB: options.indexedDB });
|
const state = await this._channel.storageState({ indexedDB: options.indexedDB });
|
||||||
if (options.path) {
|
if (options.path) {
|
||||||
await mkdirIfNeeded(options.path);
|
await mkdirIfNeeded(this._platform, options.path);
|
||||||
await fs.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
|
await this._platform.fs().promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
@ -484,7 +485,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
const needCompressed = harParams.path.endsWith('.zip');
|
const needCompressed = harParams.path.endsWith('.zip');
|
||||||
if (isCompressed && !needCompressed) {
|
if (isCompressed && !needCompressed) {
|
||||||
await artifact.saveAs(harParams.path + '.tmp');
|
await artifact.saveAs(harParams.path + '.tmp');
|
||||||
await this._connection.localUtils()._channel.harUnzip({ zipFile: harParams.path + '.tmp', harFile: harParams.path });
|
await this._connection.localUtils().harUnzip({ zipFile: harParams.path + '.tmp', harFile: harParams.path });
|
||||||
} else {
|
} else {
|
||||||
await artifact.saveAs(harParams.path);
|
await artifact.saveAs(harParams.path);
|
||||||
}
|
}
|
||||||
|
|
@ -500,11 +501,11 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function prepareStorageState(options: BrowserContextOptions): Promise<channels.BrowserNewContextParams['storageState']> {
|
async function prepareStorageState(platform: Platform, options: BrowserContextOptions): Promise<channels.BrowserNewContextParams['storageState']> {
|
||||||
if (typeof options.storageState !== 'string')
|
if (typeof options.storageState !== 'string')
|
||||||
return options.storageState;
|
return options.storageState;
|
||||||
try {
|
try {
|
||||||
return JSON.parse(await fs.promises.readFile(options.storageState, 'utf8'));
|
return JSON.parse(await platform.fs().promises.readFile(options.storageState, 'utf8'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rewriteErrorMessage(e, `Error reading storage state from ${options.storageState}:\n` + e.message);
|
rewriteErrorMessage(e, `Error reading storage state from ${options.storageState}:\n` + e.message);
|
||||||
throw e;
|
throw e;
|
||||||
|
|
@ -524,7 +525,7 @@ function prepareRecordHarOptions(options: BrowserContextOptions['recordHar']): c
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function prepareBrowserContextParams(options: BrowserContextOptions): Promise<channels.BrowserNewContextParams> {
|
export async function prepareBrowserContextParams(platform: Platform, options: BrowserContextOptions): Promise<channels.BrowserNewContextParams> {
|
||||||
if (options.videoSize && !options.videosPath)
|
if (options.videoSize && !options.videosPath)
|
||||||
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
|
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
|
||||||
if (options.extraHTTPHeaders)
|
if (options.extraHTTPHeaders)
|
||||||
|
|
@ -534,7 +535,7 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions
|
||||||
viewport: options.viewport === null ? undefined : options.viewport,
|
viewport: options.viewport === null ? undefined : options.viewport,
|
||||||
noDefaultViewport: options.viewport === null,
|
noDefaultViewport: options.viewport === null,
|
||||||
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
|
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
|
||||||
storageState: await prepareStorageState(options),
|
storageState: await prepareStorageState(platform, options),
|
||||||
serviceWorkers: options.serviceWorkers,
|
serviceWorkers: options.serviceWorkers,
|
||||||
recordHar: prepareRecordHarOptions(options.recordHar),
|
recordHar: prepareRecordHarOptions(options.recordHar),
|
||||||
colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme,
|
colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme,
|
||||||
|
|
@ -542,7 +543,7 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions
|
||||||
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors,
|
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors,
|
||||||
contrast: options.contrast === null ? 'no-override' : options.contrast,
|
contrast: options.contrast === null ? 'no-override' : options.contrast,
|
||||||
acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads),
|
acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads),
|
||||||
clientCertificates: await toClientCertificatesProtocol(options.clientCertificates),
|
clientCertificates: await toClientCertificatesProtocol(platform, options.clientCertificates),
|
||||||
};
|
};
|
||||||
if (!contextParams.recordVideo && options.videosPath) {
|
if (!contextParams.recordVideo && options.videosPath) {
|
||||||
contextParams.recordVideo = {
|
contextParams.recordVideo = {
|
||||||
|
|
@ -551,7 +552,7 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (contextParams.recordVideo && contextParams.recordVideo.dir)
|
if (contextParams.recordVideo && contextParams.recordVideo.dir)
|
||||||
contextParams.recordVideo.dir = path.resolve(process.cwd(), contextParams.recordVideo.dir);
|
contextParams.recordVideo.dir = platform.path().resolve(process.cwd(), contextParams.recordVideo.dir);
|
||||||
return contextParams;
|
return contextParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -563,7 +564,7 @@ function toAcceptDownloadsProtocol(acceptDownloads?: boolean) {
|
||||||
return 'deny';
|
return 'deny';
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toClientCertificatesProtocol(certs?: BrowserContextOptions['clientCertificates']): Promise<channels.PlaywrightNewRequestParams['clientCertificates']> {
|
export async function toClientCertificatesProtocol(platform: Platform, certs?: BrowserContextOptions['clientCertificates']): Promise<channels.PlaywrightNewRequestParams['clientCertificates']> {
|
||||||
if (!certs)
|
if (!certs)
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
||||||
|
|
@ -571,7 +572,7 @@ export async function toClientCertificatesProtocol(certs?: BrowserContextOptions
|
||||||
if (value)
|
if (value)
|
||||||
return value;
|
return value;
|
||||||
if (path)
|
if (path)
|
||||||
return await fs.promises.readFile(path);
|
return await platform.fs().promises.readFile(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
return await Promise.all(certs.map(async cert => ({
|
return await Promise.all(certs.map(async cert => ({
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,16 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
import { Browser } from './browser';
|
import { Browser } from './browser';
|
||||||
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
import { BrowserContext, prepareBrowserContextParams } from './browserContext';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { envObjectToArray } from './clientHelper';
|
import { envObjectToArray } from './clientHelper';
|
||||||
import { Connection } from './connection';
|
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { assert, headersObjectToArray, monotonicTime } from '../utils';
|
import { assert } from '../utils/debug';
|
||||||
|
import { headersObjectToArray } from '../utils/headers';
|
||||||
|
import { monotonicTime } from '../utils/time';
|
||||||
import { raceAgainstDeadline } from '../utils/timeoutRunner';
|
import { raceAgainstDeadline } from '../utils/timeoutRunner';
|
||||||
|
|
||||||
import type { Playwright } from './playwright';
|
import type { Playwright } from './playwright';
|
||||||
|
|
@ -90,14 +93,14 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
|
const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
|
||||||
assert(!(options as any).port, 'Cannot specify a port without launching as a server.');
|
assert(!(options as any).port, 'Cannot specify a port without launching as a server.');
|
||||||
options = { ...this._playwright._defaultLaunchOptions, ...this._playwright._defaultContextOptions, ...options };
|
options = { ...this._playwright._defaultLaunchOptions, ...this._playwright._defaultContextOptions, ...options };
|
||||||
const contextParams = await prepareBrowserContextParams(options);
|
const contextParams = await prepareBrowserContextParams(this._platform, options);
|
||||||
const persistentParams: channels.BrowserTypeLaunchPersistentContextParams = {
|
const persistentParams: channels.BrowserTypeLaunchPersistentContextParams = {
|
||||||
...contextParams,
|
...contextParams,
|
||||||
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
|
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
|
||||||
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
|
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
|
||||||
env: options.env ? envObjectToArray(options.env) : undefined,
|
env: options.env ? envObjectToArray(options.env) : undefined,
|
||||||
channel: options.channel,
|
channel: options.channel,
|
||||||
userDataDir,
|
userDataDir: path.isAbsolute(userDataDir) ? userDataDir : path.resolve(userDataDir),
|
||||||
};
|
};
|
||||||
return await this._wrapApiCall(async () => {
|
return await this._wrapApiCall(async () => {
|
||||||
const result = await this._channel.launchPersistentContext(persistentParams);
|
const result = await this._channel.launchPersistentContext(persistentParams);
|
||||||
|
|
@ -131,40 +134,16 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
};
|
};
|
||||||
if ((params as any).__testHookRedirectPortForwarding)
|
if ((params as any).__testHookRedirectPortForwarding)
|
||||||
connectParams.socksProxyRedirectPortForTest = (params as any).__testHookRedirectPortForwarding;
|
connectParams.socksProxyRedirectPortForTest = (params as any).__testHookRedirectPortForwarding;
|
||||||
const { pipe, headers: connectHeaders } = await localUtils._channel.connect(connectParams);
|
const connection = await localUtils.connect(connectParams);
|
||||||
const closePipe = () => pipe.close().catch(() => {});
|
|
||||||
const connection = new Connection(localUtils, this._instrumentation);
|
|
||||||
connection.markAsRemote();
|
|
||||||
connection.on('close', closePipe);
|
|
||||||
|
|
||||||
let browser: Browser;
|
let browser: Browser;
|
||||||
let closeError: string | undefined;
|
connection.on('close', () => {
|
||||||
const onPipeClosed = (reason?: string) => {
|
|
||||||
// Emulate all pages, contexts and the browser closing upon disconnect.
|
// Emulate all pages, contexts and the browser closing upon disconnect.
|
||||||
for (const context of browser?.contexts() || []) {
|
for (const context of browser?.contexts() || []) {
|
||||||
for (const page of context.pages())
|
for (const page of context.pages())
|
||||||
page._onClose();
|
page._onClose();
|
||||||
context._onClose();
|
context._onClose();
|
||||||
}
|
}
|
||||||
connection.close(reason || closeError);
|
|
||||||
// Give a chance to any API call promises to reject upon page/context closure.
|
|
||||||
// This happens naturally when we receive page.onClose and browser.onClose from the server
|
|
||||||
// in separate tasks. However, upon pipe closure we used to dispatch them all synchronously
|
|
||||||
// here and promises did not have a chance to reject.
|
|
||||||
// The order of rejects vs closure is a part of the API contract and our test runner
|
|
||||||
// relies on it to attribute rejections to the right test.
|
|
||||||
setTimeout(() => browser?._didClose(), 0);
|
setTimeout(() => browser?._didClose(), 0);
|
||||||
};
|
|
||||||
pipe.on('closed', params => onPipeClosed(params.reason));
|
|
||||||
connection.onmessage = message => this._wrapApiCall(() => pipe.send({ message }).catch(() => onPipeClosed()), /* isInternal */ true);
|
|
||||||
|
|
||||||
pipe.on('message', ({ message }) => {
|
|
||||||
try {
|
|
||||||
connection!.dispatch(message);
|
|
||||||
} catch (e) {
|
|
||||||
closeError = String(e);
|
|
||||||
closePipe();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await raceAgainstDeadline(async () => {
|
const result = await raceAgainstDeadline(async () => {
|
||||||
|
|
@ -174,21 +153,20 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
|
|
||||||
const playwright = await connection!.initializePlaywright();
|
const playwright = await connection!.initializePlaywright();
|
||||||
if (!playwright._initializer.preLaunchedBrowser) {
|
if (!playwright._initializer.preLaunchedBrowser) {
|
||||||
closePipe();
|
connection.close();
|
||||||
throw new Error('Malformed endpoint. Did you use BrowserType.launchServer method?');
|
throw new Error('Malformed endpoint. Did you use BrowserType.launchServer method?');
|
||||||
}
|
}
|
||||||
playwright._setSelectors(this._playwright.selectors);
|
playwright._setSelectors(this._playwright.selectors);
|
||||||
browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
|
browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
|
||||||
this._didLaunchBrowser(browser, {}, logger);
|
this._didLaunchBrowser(browser, {}, logger);
|
||||||
browser._shouldCloseConnectionOnClose = true;
|
browser._shouldCloseConnectionOnClose = true;
|
||||||
browser._connectHeaders = connectHeaders;
|
browser.on(Events.Browser.Disconnected, () => connection.close());
|
||||||
browser.on(Events.Browser.Disconnected, () => this._wrapApiCall(() => closePipe(), /* isInternal */ true));
|
|
||||||
return browser;
|
return browser;
|
||||||
}, deadline);
|
}, deadline);
|
||||||
if (!result.timedOut) {
|
if (!result.timedOut) {
|
||||||
return result.result;
|
return result.result;
|
||||||
} else {
|
} else {
|
||||||
closePipe();
|
connection.close();
|
||||||
throw new Error(`Timeout ${params.timeout}ms exceeded`);
|
throw new Error(`Timeout ${params.timeout}ms exceeded`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import { EventEmitter } from './eventEmitter';
|
import { EventEmitter } from './eventEmitter';
|
||||||
import { ValidationError, maybeFindValidator } from '../protocol/validator';
|
import { ValidationError, maybeFindValidator } from '../protocol/validator';
|
||||||
import { isUnderTest } from '../utils';
|
import { isUnderTest } from '../utils/debug';
|
||||||
import { debugLogger } from '../utils/debugLogger';
|
import { debugLogger } from '../utils/debugLogger';
|
||||||
import { captureLibraryStackTrace, stringifyStackFrames } from '../utils/stackTrace';
|
import { captureLibraryStackTrace, stringifyStackFrames } from '../utils/stackTrace';
|
||||||
import { zones } from '../utils/zones';
|
import { zones } from '../utils/zones';
|
||||||
|
|
@ -25,6 +25,7 @@ import type { ClientInstrumentation } from './clientInstrumentation';
|
||||||
import type { Connection } from './connection';
|
import type { Connection } from './connection';
|
||||||
import type { Logger } from './types';
|
import type { Logger } from './types';
|
||||||
import type { ValidatorContext } from '../protocol/validator';
|
import type { ValidatorContext } from '../protocol/validator';
|
||||||
|
import type { Platform } from '../utils/platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
type Listener = (...args: any[]) => void;
|
type Listener = (...args: any[]) => void;
|
||||||
|
|
@ -39,6 +40,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
||||||
readonly _channel: T;
|
readonly _channel: T;
|
||||||
readonly _initializer: channels.InitializerTraits<T>;
|
readonly _initializer: channels.InitializerTraits<T>;
|
||||||
_logger: Logger | undefined;
|
_logger: Logger | undefined;
|
||||||
|
readonly _platform: Platform;
|
||||||
readonly _instrumentation: ClientInstrumentation;
|
readonly _instrumentation: ClientInstrumentation;
|
||||||
private _eventToSubscriptionMapping: Map<string, string> = new Map();
|
private _eventToSubscriptionMapping: Map<string, string> = new Map();
|
||||||
private _isInternalType = false;
|
private _isInternalType = false;
|
||||||
|
|
@ -52,6 +54,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
|
||||||
this._guid = guid;
|
this._guid = guid;
|
||||||
this._parent = parent instanceof ChannelOwner ? parent : undefined;
|
this._parent = parent instanceof ChannelOwner ? parent : undefined;
|
||||||
this._instrumentation = this._connection._instrumentation;
|
this._instrumentation = this._connection._instrumentation;
|
||||||
|
this._platform = this._connection.platform;
|
||||||
|
|
||||||
this._connection._objects.set(guid, this);
|
this._connection._objects.set(guid, this);
|
||||||
if (this._parent) {
|
if (this._parent) {
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import { isString } from '../utils/rtti';
|
||||||
|
|
||||||
import { isString } from '../utils';
|
|
||||||
|
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
|
import type { Platform } from '../utils/platform';
|
||||||
|
|
||||||
export function envObjectToArray(env: types.Env): { name: string, value: string }[] {
|
export function envObjectToArray(env: types.Env): { name: string, value: string }[] {
|
||||||
const result: { name: string, value: string }[] = [];
|
const result: { name: string, value: string }[] = [];
|
||||||
|
|
@ -30,7 +29,7 @@ export function envObjectToArray(env: types.Env): { name: string, value: string
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function evaluationScript(fun: Function | string | { path?: string, content?: string }, arg?: any, addSourceUrl: boolean = true): Promise<string> {
|
export async function evaluationScript(platform: Platform, fun: Function | string | { path?: string, content?: string }, arg?: any, addSourceUrl: boolean = true): Promise<string> {
|
||||||
if (typeof fun === 'function') {
|
if (typeof fun === 'function') {
|
||||||
const source = fun.toString();
|
const source = fun.toString();
|
||||||
const argString = Object.is(arg, undefined) ? 'undefined' : JSON.stringify(arg);
|
const argString = Object.is(arg, undefined) ? 'undefined' : JSON.stringify(arg);
|
||||||
|
|
@ -43,7 +42,7 @@ export async function evaluationScript(fun: Function | string | { path?: string,
|
||||||
if (fun.content !== undefined)
|
if (fun.content !== undefined)
|
||||||
return fun.content;
|
return fun.content;
|
||||||
if (fun.path !== undefined) {
|
if (fun.path !== undefined) {
|
||||||
let source = await fs.promises.readFile(fun.path, 'utf8');
|
let source = await platform.fs().promises.readFile(fun.path, 'utf8');
|
||||||
if (addSourceUrl)
|
if (addSourceUrl)
|
||||||
source = addSourceUrlToScript(source, fun.path);
|
source = addSourceUrlToScript(source, fun.path);
|
||||||
return source;
|
return source;
|
||||||
|
|
|
||||||
|
|
@ -42,11 +42,14 @@ import { Tracing } from './tracing';
|
||||||
import { Worker } from './worker';
|
import { Worker } from './worker';
|
||||||
import { WritableStream } from './writableStream';
|
import { WritableStream } from './writableStream';
|
||||||
import { ValidationError, findValidator } from '../protocol/validator';
|
import { ValidationError, findValidator } from '../protocol/validator';
|
||||||
import { formatCallLog, rewriteErrorMessage, zones } from '../utils';
|
|
||||||
import { debugLogger } from '../utils/debugLogger';
|
import { debugLogger } from '../utils/debugLogger';
|
||||||
|
import { formatCallLog, rewriteErrorMessage } from '../utils/stackTrace';
|
||||||
|
import { zones } from '../utils/zones';
|
||||||
|
|
||||||
import type { ClientInstrumentation } from './clientInstrumentation';
|
import type { ClientInstrumentation } from './clientInstrumentation';
|
||||||
|
import type { HeadersArray } from './types';
|
||||||
import type { ValidatorContext } from '../protocol/validator';
|
import type { ValidatorContext } from '../protocol/validator';
|
||||||
|
import type { Platform } from '../utils/platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
class Root extends ChannelOwner<channels.RootChannel> {
|
class Root extends ChannelOwner<channels.RootChannel> {
|
||||||
|
|
@ -78,12 +81,17 @@ export class Connection extends EventEmitter {
|
||||||
toImpl: ((client: ChannelOwner) => any) | undefined;
|
toImpl: ((client: ChannelOwner) => any) | undefined;
|
||||||
private _tracingCount = 0;
|
private _tracingCount = 0;
|
||||||
readonly _instrumentation: ClientInstrumentation;
|
readonly _instrumentation: ClientInstrumentation;
|
||||||
|
readonly platform: Platform;
|
||||||
|
// Used from @playwright/test fixtures -> TODO remove?
|
||||||
|
readonly headers: HeadersArray;
|
||||||
|
|
||||||
constructor(localUtils: LocalUtils | undefined, instrumentation: ClientInstrumentation | undefined) {
|
constructor(localUtils: LocalUtils | undefined, platform: Platform, instrumentation: ClientInstrumentation | undefined, headers: HeadersArray) {
|
||||||
super();
|
super();
|
||||||
this._instrumentation = instrumentation || createInstrumentation();
|
this._instrumentation = instrumentation || createInstrumentation();
|
||||||
this._localUtils = localUtils;
|
this._localUtils = localUtils;
|
||||||
|
this.platform = platform;
|
||||||
this._rootObject = new Root(this);
|
this._rootObject = new Root(this);
|
||||||
|
this.headers = headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
markAsRemote() {
|
markAsRemote() {
|
||||||
|
|
@ -138,7 +146,7 @@ export class Connection extends EventEmitter {
|
||||||
const location = frames[0] ? { file: frames[0].file, line: frames[0].line, column: frames[0].column } : undefined;
|
const location = frames[0] ? { file: frames[0].file, line: frames[0].line, column: frames[0].column } : undefined;
|
||||||
const metadata: channels.Metadata = { apiName, location, internal: !apiName, stepId };
|
const metadata: channels.Metadata = { apiName, location, internal: !apiName, stepId };
|
||||||
if (this._tracingCount && frames && type !== 'LocalUtils')
|
if (this._tracingCount && frames && type !== 'LocalUtils')
|
||||||
this._localUtils?._channel.addStackToTracingNoReply({ callData: { stack: frames, id } }).catch(() => {});
|
this._localUtils?.addStackToTracingNoReply({ callData: { stack: frames, id } }).catch(() => {});
|
||||||
// We need to exit zones before calling into the server, otherwise
|
// We need to exit zones before calling into the server, otherwise
|
||||||
// when we receive events from the server, we would be in an API zone.
|
// when we receive events from the server, we would be in an API zone.
|
||||||
zones.empty().run(() => this.onmessage({ ...message, metadata }));
|
zones.empty().run(() => this.onmessage({ ...message, metadata }));
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as util from 'util';
|
|
||||||
|
|
||||||
import { JSHandle } from './jsHandle';
|
import { JSHandle } from './jsHandle';
|
||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
|
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
|
import type { Platform } from '../utils/platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
type ConsoleMessageLocation = channels.BrowserContextConsoleEvent['location'];
|
type ConsoleMessageLocation = channels.BrowserContextConsoleEvent['location'];
|
||||||
|
|
@ -29,9 +28,11 @@ export class ConsoleMessage implements api.ConsoleMessage {
|
||||||
private _page: Page | null;
|
private _page: Page | null;
|
||||||
private _event: channels.BrowserContextConsoleEvent | channels.ElectronApplicationConsoleEvent;
|
private _event: channels.BrowserContextConsoleEvent | channels.ElectronApplicationConsoleEvent;
|
||||||
|
|
||||||
constructor(event: channels.BrowserContextConsoleEvent | channels.ElectronApplicationConsoleEvent) {
|
constructor(platform: Platform, event: channels.BrowserContextConsoleEvent | channels.ElectronApplicationConsoleEvent) {
|
||||||
this._page = ('page' in event && event.page) ? Page.from(event.page) : null;
|
this._page = ('page' in event && event.page) ? Page.from(event.page) : null;
|
||||||
this._event = event;
|
this._event = event;
|
||||||
|
if (platform.inspectCustom)
|
||||||
|
(this as any)[platform.inspectCustom] = () => this._inspect();
|
||||||
}
|
}
|
||||||
|
|
||||||
page() {
|
page() {
|
||||||
|
|
@ -54,7 +55,7 @@ export class ConsoleMessage implements api.ConsoleMessage {
|
||||||
return this._event.location;
|
return this._event.location;
|
||||||
}
|
}
|
||||||
|
|
||||||
[util.inspect.custom]() {
|
private _inspect() {
|
||||||
return this.text();
|
return this.text();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export class Electron extends ChannelOwner<channels.ElectronChannel> implements
|
||||||
|
|
||||||
async launch(options: ElectronOptions = {}): Promise<ElectronApplication> {
|
async launch(options: ElectronOptions = {}): Promise<ElectronApplication> {
|
||||||
const params: channels.ElectronLaunchParams = {
|
const params: channels.ElectronLaunchParams = {
|
||||||
...await prepareBrowserContextParams(options),
|
...await prepareBrowserContextParams(this._platform, options),
|
||||||
env: envObjectToArray(options.env ? options.env : process.env),
|
env: envObjectToArray(options.env ? options.env : process.env),
|
||||||
tracesDir: options.tracesDir,
|
tracesDir: options.tracesDir,
|
||||||
};
|
};
|
||||||
|
|
@ -81,7 +81,7 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
|
||||||
this._channel.on('close', () => {
|
this._channel.on('close', () => {
|
||||||
this.emit(Events.ElectronApplication.Close);
|
this.emit(Events.ElectronApplication.Close);
|
||||||
});
|
});
|
||||||
this._channel.on('console', event => this.emit(Events.ElectronApplication.Console, new ConsoleMessage(event)));
|
this._channel.on('console', event => this.emit(Events.ElectronApplication.Console, new ConsoleMessage(this._platform, event)));
|
||||||
this._setEventToSubscriptionMapping(new Map<string, channels.ElectronApplicationUpdateSubscriptionParams['event']>([
|
this._setEventToSubscriptionMapping(new Map<string, channels.ElectronApplicationUpdateSubscriptionParams['event']>([
|
||||||
[Events.ElectronApplication.Console, 'console'],
|
[Events.ElectronApplication.Console, 'console'],
|
||||||
]));
|
]));
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,14 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
import { pipeline } from 'stream';
|
import { pipeline } from 'stream';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
|
|
||||||
import { Frame } from './frame';
|
import { Frame } from './frame';
|
||||||
import { JSHandle, parseResult, serializeArgument } from './jsHandle';
|
import { JSHandle, parseResult, serializeArgument } from './jsHandle';
|
||||||
import { assert, isString } from '../utils';
|
import { assert } from '../utils/debug';
|
||||||
import { fileUploadSizeLimit, mkdirIfNeeded } from '../utils/fileUtils';
|
import { fileUploadSizeLimit, mkdirIfNeeded } from '../utils/fileUtils';
|
||||||
|
import { isString } from '../utils/rtti';
|
||||||
import { mime } from '../utilsBundle';
|
import { mime } from '../utilsBundle';
|
||||||
import { WritableStream } from './writableStream';
|
import { WritableStream } from './writableStream';
|
||||||
|
|
||||||
|
|
@ -32,6 +31,7 @@ import type { Locator } from './locator';
|
||||||
import type { FilePayload, Rect, SelectOption, SelectOptionOptions } from './types';
|
import type { FilePayload, Rect, SelectOption, SelectOptionOptions } from './types';
|
||||||
import type * as structs from '../../types/structs';
|
import type * as structs from '../../types/structs';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
|
import type { Platform } from '../utils/platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
const pipelineAsync = promisify(pipeline);
|
const pipelineAsync = promisify(pipeline);
|
||||||
|
|
@ -156,7 +156,7 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> implements
|
||||||
const frame = await this.ownerFrame();
|
const frame = await this.ownerFrame();
|
||||||
if (!frame)
|
if (!frame)
|
||||||
throw new Error('Cannot set input files to detached element');
|
throw new Error('Cannot set input files to detached element');
|
||||||
const converted = await convertInputFiles(files, frame.page().context());
|
const converted = await convertInputFiles(this._platform, files, frame.page().context());
|
||||||
await this._elementChannel.setInputFiles({ ...converted, ...options });
|
await this._elementChannel.setInputFiles({ ...converted, ...options });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,20 +192,21 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> implements
|
||||||
return value === undefined ? null : value;
|
return value === undefined ? null : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async screenshot(options: Omit<channels.ElementHandleScreenshotOptions, 'mask'> & { path?: string, mask?: Locator[] } = {}): Promise<Buffer> {
|
async screenshot(options: Omit<channels.ElementHandleScreenshotOptions, 'mask'> & { path?: string, mask?: api.Locator[] } = {}): Promise<Buffer> {
|
||||||
|
const mask = options.mask as Locator[] | undefined;
|
||||||
const copy: channels.ElementHandleScreenshotOptions = { ...options, mask: undefined };
|
const copy: channels.ElementHandleScreenshotOptions = { ...options, mask: undefined };
|
||||||
if (!copy.type)
|
if (!copy.type)
|
||||||
copy.type = determineScreenshotType(options);
|
copy.type = determineScreenshotType(options);
|
||||||
if (options.mask) {
|
if (mask) {
|
||||||
copy.mask = options.mask.map(locator => ({
|
copy.mask = mask.map(locator => ({
|
||||||
frame: locator._frame._channel,
|
frame: locator._frame._channel,
|
||||||
selector: locator._selector,
|
selector: locator._selector,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
const result = await this._elementChannel.screenshot(copy);
|
const result = await this._elementChannel.screenshot(copy);
|
||||||
if (options.path) {
|
if (options.path) {
|
||||||
await mkdirIfNeeded(options.path);
|
await mkdirIfNeeded(this._platform, options.path);
|
||||||
await fs.promises.writeFile(options.path, result.binary);
|
await this._platform.fs().promises.writeFile(options.path, result.binary);
|
||||||
}
|
}
|
||||||
return result.binary;
|
return result.binary;
|
||||||
}
|
}
|
||||||
|
|
@ -263,18 +264,18 @@ function filePayloadExceedsSizeLimit(payloads: FilePayload[]) {
|
||||||
return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= fileUploadSizeLimit;
|
return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= fileUploadSizeLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolvePathsAndDirectoryForInputFiles(items: string[]): Promise<[string[] | undefined, string | undefined]> {
|
async function resolvePathsAndDirectoryForInputFiles(platform: Platform, items: string[]): Promise<[string[] | undefined, string | undefined]> {
|
||||||
let localPaths: string[] | undefined;
|
let localPaths: string[] | undefined;
|
||||||
let localDirectory: string | undefined;
|
let localDirectory: string | undefined;
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const stat = await fs.promises.stat(item as string);
|
const stat = await platform.fs().promises.stat(item as string);
|
||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
if (localDirectory)
|
if (localDirectory)
|
||||||
throw new Error('Multiple directories are not supported');
|
throw new Error('Multiple directories are not supported');
|
||||||
localDirectory = path.resolve(item as string);
|
localDirectory = platform.path().resolve(item as string);
|
||||||
} else {
|
} else {
|
||||||
localPaths ??= [];
|
localPaths ??= [];
|
||||||
localPaths.push(path.resolve(item as string));
|
localPaths.push(platform.path().resolve(item as string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (localPaths?.length && localDirectory)
|
if (localPaths?.length && localDirectory)
|
||||||
|
|
@ -282,30 +283,30 @@ async function resolvePathsAndDirectoryForInputFiles(items: string[]): Promise<[
|
||||||
return [localPaths, localDirectory];
|
return [localPaths, localDirectory];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function convertInputFiles(files: string | FilePayload | string[] | FilePayload[], context: BrowserContext): Promise<SetInputFilesFiles> {
|
export async function convertInputFiles(platform: Platform, files: string | FilePayload | string[] | FilePayload[], context: BrowserContext): Promise<SetInputFilesFiles> {
|
||||||
const items: (string | FilePayload)[] = Array.isArray(files) ? files.slice() : [files];
|
const items: (string | FilePayload)[] = Array.isArray(files) ? files.slice() : [files];
|
||||||
|
|
||||||
if (items.some(item => typeof item === 'string')) {
|
if (items.some(item => typeof item === 'string')) {
|
||||||
if (!items.every(item => typeof item === 'string'))
|
if (!items.every(item => typeof item === 'string'))
|
||||||
throw new Error('File paths cannot be mixed with buffers');
|
throw new Error('File paths cannot be mixed with buffers');
|
||||||
|
|
||||||
const [localPaths, localDirectory] = await resolvePathsAndDirectoryForInputFiles(items);
|
const [localPaths, localDirectory] = await resolvePathsAndDirectoryForInputFiles(platform, items);
|
||||||
|
|
||||||
if (context._connection.isRemote()) {
|
if (context._connection.isRemote()) {
|
||||||
const files = localDirectory ? (await fs.promises.readdir(localDirectory, { withFileTypes: true, recursive: true })).filter(f => f.isFile()).map(f => path.join(f.path, f.name)) : localPaths!;
|
const files = localDirectory ? (await platform.fs().promises.readdir(localDirectory, { withFileTypes: true, recursive: true })).filter(f => f.isFile()).map(f => platform.path().join(f.path, f.name)) : localPaths!;
|
||||||
const { writableStreams, rootDir } = await context._wrapApiCall(async () => context._channel.createTempFiles({
|
const { writableStreams, rootDir } = await context._wrapApiCall(async () => context._channel.createTempFiles({
|
||||||
rootDirName: localDirectory ? path.basename(localDirectory) : undefined,
|
rootDirName: localDirectory ? platform.path().basename(localDirectory) : undefined,
|
||||||
items: await Promise.all(files.map(async file => {
|
items: await Promise.all(files.map(async file => {
|
||||||
const lastModifiedMs = (await fs.promises.stat(file)).mtimeMs;
|
const lastModifiedMs = (await platform.fs().promises.stat(file)).mtimeMs;
|
||||||
return {
|
return {
|
||||||
name: localDirectory ? path.relative(localDirectory, file) : path.basename(file),
|
name: localDirectory ? platform.path().relative(localDirectory, file) : platform.path().basename(file),
|
||||||
lastModifiedMs
|
lastModifiedMs
|
||||||
};
|
};
|
||||||
})),
|
})),
|
||||||
}), true);
|
}), true);
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
const writable = WritableStream.from(writableStreams[i]);
|
const writable = WritableStream.from(writableStreams[i]);
|
||||||
await pipelineAsync(fs.createReadStream(files[i]), writable.stream());
|
await pipelineAsync(platform.fs().createReadStream(files[i]), writable.stream());
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
directoryStream: rootDir,
|
directoryStream: rootDir,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { parseSerializedValue, serializeValue } from '../protocol/serializers';
|
import { parseSerializedValue, serializeValue } from '../protocol/serializers';
|
||||||
import { isError } from '../utils';
|
import { isError } from '../utils/rtti';
|
||||||
|
|
||||||
import type { SerializedError } from '@protocol/channels';
|
import type { SerializedError } from '@protocol/channels';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
import { EventEmitter as OriginalEventEmitter } from 'events';
|
import { EventEmitter as OriginalEventEmitter } from 'events';
|
||||||
|
|
||||||
import { isUnderTest } from '../utils';
|
import { isUnderTest } from '../utils/debug';
|
||||||
|
|
||||||
import type { EventEmitter as EventEmitterType } from 'events';
|
import type { EventEmitter as EventEmitterType } from 'events';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,24 +14,24 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as util from 'util';
|
|
||||||
|
|
||||||
import { assert, headersObjectToArray, isString } from '../utils';
|
|
||||||
import { toClientCertificatesProtocol } from './browserContext';
|
import { toClientCertificatesProtocol } from './browserContext';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { TargetClosedError, isTargetClosedError } from './errors';
|
import { TargetClosedError, isTargetClosedError } from './errors';
|
||||||
import { RawHeaders } from './network';
|
import { RawHeaders } from './network';
|
||||||
import { Tracing } from './tracing';
|
import { Tracing } from './tracing';
|
||||||
|
import { assert } from '../utils/debug';
|
||||||
import { mkdirIfNeeded } from '../utils/fileUtils';
|
import { mkdirIfNeeded } from '../utils/fileUtils';
|
||||||
|
import { headersObjectToArray } from '../utils/headers';
|
||||||
|
import { isString } from '../utils/rtti';
|
||||||
|
|
||||||
import type { Playwright } from './playwright';
|
import type { Playwright } from './playwright';
|
||||||
import type { ClientCertificate, FilePayload, Headers, SetStorageState, StorageState } from './types';
|
import type { ClientCertificate, FilePayload, Headers, SetStorageState, StorageState } from './types';
|
||||||
import type { Serializable } from '../../types/structs';
|
import type { Serializable } from '../../types/structs';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type { HeadersArray, NameValue } from '../common/types';
|
import type { HeadersArray, NameValue } from '../common/types';
|
||||||
|
import type { Platform } from '../utils/platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
import type * as fs from 'fs';
|
||||||
|
|
||||||
export type FetchOptions = {
|
export type FetchOptions = {
|
||||||
params?: { [key: string]: string | number | boolean; } | URLSearchParams | string,
|
params?: { [key: string]: string | number | boolean; } | URLSearchParams | string,
|
||||||
|
|
@ -70,14 +70,14 @@ export class APIRequest implements api.APIRequest {
|
||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
const storageState = typeof options.storageState === 'string' ?
|
const storageState = typeof options.storageState === 'string' ?
|
||||||
JSON.parse(await fs.promises.readFile(options.storageState, 'utf8')) :
|
JSON.parse(await this._playwright._platform.fs().promises.readFile(options.storageState, 'utf8')) :
|
||||||
options.storageState;
|
options.storageState;
|
||||||
const context = APIRequestContext.from((await this._playwright._channel.newRequest({
|
const context = APIRequestContext.from((await this._playwright._channel.newRequest({
|
||||||
...options,
|
...options,
|
||||||
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
|
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
|
||||||
storageState,
|
storageState,
|
||||||
tracesDir: this._playwright._defaultLaunchOptions?.tracesDir, // We do not expose tracesDir in the API, so do not allow options to accidentally override it.
|
tracesDir: this._playwright._defaultLaunchOptions?.tracesDir, // We do not expose tracesDir in the API, so do not allow options to accidentally override it.
|
||||||
clientCertificates: await toClientCertificatesProtocol(options.clientCertificates),
|
clientCertificates: await toClientCertificatesProtocol(this._playwright._platform, options.clientCertificates),
|
||||||
})).request);
|
})).request);
|
||||||
this._contexts.add(context);
|
this._contexts.add(context);
|
||||||
context._request = this;
|
context._request = this;
|
||||||
|
|
@ -232,7 +232,7 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
|
||||||
} else {
|
} else {
|
||||||
// Convert file-like values to ServerFilePayload structs.
|
// Convert file-like values to ServerFilePayload structs.
|
||||||
for (const [name, value] of Object.entries(options.multipart))
|
for (const [name, value] of Object.entries(options.multipart))
|
||||||
multipartData.push(await toFormField(name, value));
|
multipartData.push(await toFormField(this._platform, name, value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (postDataBuffer === undefined && jsonData === undefined && formData === undefined && multipartData === undefined)
|
if (postDataBuffer === undefined && jsonData === undefined && formData === undefined && multipartData === undefined)
|
||||||
|
|
@ -264,23 +264,24 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
|
||||||
async storageState(options: { path?: string, indexedDB?: boolean } = {}): Promise<StorageState> {
|
async storageState(options: { path?: string, indexedDB?: boolean } = {}): Promise<StorageState> {
|
||||||
const state = await this._channel.storageState({ indexedDB: options.indexedDB });
|
const state = await this._channel.storageState({ indexedDB: options.indexedDB });
|
||||||
if (options.path) {
|
if (options.path) {
|
||||||
await mkdirIfNeeded(options.path);
|
await mkdirIfNeeded(this._platform, options.path);
|
||||||
await fs.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
|
await this._platform.fs().promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toFormField(name: string, value: string|number|boolean|fs.ReadStream|FilePayload): Promise<channels.FormField> {
|
async function toFormField(platform: Platform, name: string, value: string | number | boolean | fs.ReadStream | FilePayload): Promise<channels.FormField> {
|
||||||
|
const typeOfValue = typeof value;
|
||||||
if (isFilePayload(value)) {
|
if (isFilePayload(value)) {
|
||||||
const payload = value as FilePayload;
|
const payload = value as FilePayload;
|
||||||
if (!Buffer.isBuffer(payload.buffer))
|
if (!Buffer.isBuffer(payload.buffer))
|
||||||
throw new Error(`Unexpected buffer type of 'data.${name}'`);
|
throw new Error(`Unexpected buffer type of 'data.${name}'`);
|
||||||
return { name, file: filePayloadToJson(payload) };
|
return { name, file: filePayloadToJson(payload) };
|
||||||
} else if (value instanceof fs.ReadStream) {
|
} else if (typeOfValue === 'string' || typeOfValue === 'number' || typeOfValue === 'boolean') {
|
||||||
return { name, file: await readStreamToJson(value as fs.ReadStream) };
|
|
||||||
} else {
|
|
||||||
return { name, value: String(value) };
|
return { name, value: String(value) };
|
||||||
|
} else {
|
||||||
|
return { name, file: await readStreamToJson(platform, value as fs.ReadStream) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,6 +308,9 @@ export class APIResponse implements api.APIResponse {
|
||||||
this._request = context;
|
this._request = context;
|
||||||
this._initializer = initializer;
|
this._initializer = initializer;
|
||||||
this._headers = new RawHeaders(this._initializer.headers);
|
this._headers = new RawHeaders(this._initializer.headers);
|
||||||
|
|
||||||
|
if (context._platform.inspectCustom)
|
||||||
|
(this as any)[context._platform.inspectCustom] = () => this._inspect();
|
||||||
}
|
}
|
||||||
|
|
||||||
ok(): boolean {
|
ok(): boolean {
|
||||||
|
|
@ -364,7 +368,7 @@ export class APIResponse implements api.APIResponse {
|
||||||
await this._request._channel.disposeAPIResponse({ fetchUid: this._fetchUid() });
|
await this._request._channel.disposeAPIResponse({ fetchUid: this._fetchUid() });
|
||||||
}
|
}
|
||||||
|
|
||||||
[util.inspect.custom]() {
|
private _inspect() {
|
||||||
const headers = this.headersArray().map(({ name, value }) => ` ${name}: ${value}`);
|
const headers = this.headersArray().map(({ name, value }) => ` ${name}: ${value}`);
|
||||||
return `APIResponse: ${this.status()} ${this.statusText()}\n${headers.join('\n')}`;
|
return `APIResponse: ${this.status()} ${this.statusText()}\n${headers.join('\n')}`;
|
||||||
}
|
}
|
||||||
|
|
@ -389,7 +393,7 @@ function filePayloadToJson(payload: FilePayload): ServerFilePayload {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readStreamToJson(stream: fs.ReadStream): Promise<ServerFilePayload> {
|
async function readStreamToJson(platform: Platform, stream: fs.ReadStream): Promise<ServerFilePayload> {
|
||||||
const buffer = await new Promise<Buffer>((resolve, reject) => {
|
const buffer = await new Promise<Buffer>((resolve, reject) => {
|
||||||
const chunks: Buffer[] = [];
|
const chunks: Buffer[] = [];
|
||||||
stream.on('data', chunk => chunks.push(chunk as Buffer));
|
stream.on('data', chunk => chunks.push(chunk as Buffer));
|
||||||
|
|
@ -398,7 +402,7 @@ async function readStreamToJson(stream: fs.ReadStream): Promise<ServerFilePayloa
|
||||||
});
|
});
|
||||||
const streamPath: string = Buffer.isBuffer(stream.path) ? stream.path.toString('utf8') : stream.path;
|
const streamPath: string = Buffer.isBuffer(stream.path) ? stream.path.toString('utf8') : stream.path;
|
||||||
return {
|
return {
|
||||||
name: path.basename(streamPath),
|
name: platform.path().basename(streamPath),
|
||||||
buffer,
|
buffer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,28 +16,27 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { FrameLocator, Locator, testIdAttributeName } from './locator';
|
|
||||||
import { assert } from '../utils';
|
|
||||||
import { urlMatches } from '../utils';
|
|
||||||
import { addSourceUrlToScript } from './clientHelper';
|
import { addSourceUrlToScript } from './clientHelper';
|
||||||
import { ElementHandle, convertInputFiles, convertSelectOptionValues } from './elementHandle';
|
import { ElementHandle, convertInputFiles, convertSelectOptionValues } from './elementHandle';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { JSHandle, assertMaxArguments, parseResult, serializeArgument } from './jsHandle';
|
import { JSHandle, assertMaxArguments, parseResult, serializeArgument } from './jsHandle';
|
||||||
|
import { FrameLocator, Locator, testIdAttributeName } from './locator';
|
||||||
import * as network from './network';
|
import * as network from './network';
|
||||||
import { kLifecycleEvents } from './types';
|
import { kLifecycleEvents } from './types';
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
|
import { assert } from '../utils/debug';
|
||||||
import { getByAltTextSelector, getByLabelSelector, getByPlaceholderSelector, getByRoleSelector, getByTestIdSelector, getByTextSelector, getByTitleSelector } from '../utils/isomorphic/locatorUtils';
|
import { getByAltTextSelector, getByLabelSelector, getByPlaceholderSelector, getByRoleSelector, getByTestIdSelector, getByTextSelector, getByTitleSelector } from '../utils/isomorphic/locatorUtils';
|
||||||
|
import { urlMatches } from '../utils/isomorphic/urlMatch';
|
||||||
|
|
||||||
import type { LocatorOptions } from './locator';
|
import type { LocatorOptions } from './locator';
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
import type { FilePayload, LifecycleEvent, SelectOption, SelectOptionOptions, StrictOptions, WaitForFunctionOptions } from './types';
|
import type { FilePayload, LifecycleEvent, SelectOption, SelectOptionOptions, StrictOptions, WaitForFunctionOptions } from './types';
|
||||||
import type * as structs from '../../types/structs';
|
import type * as structs from '../../types/structs';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type { URLMatch } from '../utils';
|
|
||||||
import type { ByRoleOptions } from '../utils/isomorphic/locatorUtils';
|
import type { ByRoleOptions } from '../utils/isomorphic/locatorUtils';
|
||||||
|
import type { URLMatch } from '../utils/isomorphic/urlMatch';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
export type WaitForNavigationOptions = {
|
export type WaitForNavigationOptions = {
|
||||||
|
|
@ -269,7 +268,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
|
||||||
async addScriptTag(options: { url?: string, path?: string, content?: string, type?: string } = {}): Promise<ElementHandle> {
|
async addScriptTag(options: { url?: string, path?: string, content?: string, type?: string } = {}): Promise<ElementHandle> {
|
||||||
const copy = { ...options };
|
const copy = { ...options };
|
||||||
if (copy.path) {
|
if (copy.path) {
|
||||||
copy.content = (await fs.promises.readFile(copy.path)).toString();
|
copy.content = (await this._platform.fs().promises.readFile(copy.path)).toString();
|
||||||
copy.content = addSourceUrlToScript(copy.content, copy.path);
|
copy.content = addSourceUrlToScript(copy.content, copy.path);
|
||||||
}
|
}
|
||||||
return ElementHandle.from((await this._channel.addScriptTag({ ...copy })).element);
|
return ElementHandle.from((await this._channel.addScriptTag({ ...copy })).element);
|
||||||
|
|
@ -278,7 +277,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
|
||||||
async addStyleTag(options: { url?: string; path?: string; content?: string; } = {}): Promise<ElementHandle> {
|
async addStyleTag(options: { url?: string; path?: string; content?: string; } = {}): Promise<ElementHandle> {
|
||||||
const copy = { ...options };
|
const copy = { ...options };
|
||||||
if (copy.path) {
|
if (copy.path) {
|
||||||
copy.content = (await fs.promises.readFile(copy.path)).toString();
|
copy.content = (await this._platform.fs().promises.readFile(copy.path)).toString();
|
||||||
copy.content += '/*# sourceURL=' + copy.path.replace(/\n/g, '') + '*/';
|
copy.content += '/*# sourceURL=' + copy.path.replace(/\n/g, '') + '*/';
|
||||||
}
|
}
|
||||||
return ElementHandle.from((await this._channel.addStyleTag({ ...copy })).element);
|
return ElementHandle.from((await this._channel.addStyleTag({ ...copy })).element);
|
||||||
|
|
@ -403,7 +402,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
|
||||||
}
|
}
|
||||||
|
|
||||||
async setInputFiles(selector: string, files: string | FilePayload | string[] | FilePayload[], options: channels.FrameSetInputFilesOptions = {}): Promise<void> {
|
async setInputFiles(selector: string, files: string | FilePayload | string[] | FilePayload[], options: channels.FrameSetInputFilesOptions = {}): Promise<void> {
|
||||||
const converted = await convertInputFiles(files, this.page().context());
|
const converted = await convertInputFiles(this._platform, files, this.page().context());
|
||||||
await this._channel.setInputFiles({ selector, ...converted, ...options });
|
await this._channel.setInputFiles({ selector, ...converted, ...options });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import { debugLogger } from '../utils/debugLogger';
|
||||||
import type { BrowserContext } from './browserContext';
|
import type { BrowserContext } from './browserContext';
|
||||||
import type { LocalUtils } from './localUtils';
|
import type { LocalUtils } from './localUtils';
|
||||||
import type { Route } from './network';
|
import type { Route } from './network';
|
||||||
import type { URLMatch } from '../utils';
|
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
|
import type { URLMatch } from '../utils/isomorphic/urlMatch';
|
||||||
|
|
||||||
type HarNotFoundAction = 'abort' | 'fallback';
|
type HarNotFoundAction = 'abort' | 'fallback';
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ export class HarRouter {
|
||||||
private _options: { urlMatch?: URLMatch; baseURL?: string; };
|
private _options: { urlMatch?: URLMatch; baseURL?: string; };
|
||||||
|
|
||||||
static async create(localUtils: LocalUtils, file: string, notFoundAction: HarNotFoundAction, options: { urlMatch?: URLMatch }): Promise<HarRouter> {
|
static async create(localUtils: LocalUtils, file: string, notFoundAction: HarNotFoundAction, options: { urlMatch?: URLMatch }): Promise<HarRouter> {
|
||||||
const { harId, error } = await localUtils._channel.harOpen({ file });
|
const { harId, error } = await localUtils.harOpen({ file });
|
||||||
if (error)
|
if (error)
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
return new HarRouter(localUtils, harId!, notFoundAction, options);
|
return new HarRouter(localUtils, harId!, notFoundAction, options);
|
||||||
|
|
@ -47,7 +47,7 @@ export class HarRouter {
|
||||||
private async _handle(route: Route) {
|
private async _handle(route: Route) {
|
||||||
const request = route.request();
|
const request = route.request();
|
||||||
|
|
||||||
const response = await this._localUtils._channel.harLookup({
|
const response = await this._localUtils.harLookup({
|
||||||
harId: this._harId,
|
harId: this._harId,
|
||||||
url: request.url(),
|
url: request.url(),
|
||||||
method: request.method(),
|
method: request.method(),
|
||||||
|
|
@ -103,6 +103,6 @@ export class HarRouter {
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this._localUtils._channel.harClose({ harId: this._harId }).catch(() => {});
|
this._localUtils.harClose({ harId: this._harId }).catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
|
import { Connection } from './connection';
|
||||||
|
import * as localUtils from '../utils/localUtils';
|
||||||
|
|
||||||
import type { Size } from './types';
|
import type { HeadersArray, Size } from './types';
|
||||||
|
import type { HarBackend } from '../utils/harBackend';
|
||||||
|
import type { Platform } from '../utils/platform';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
type DeviceDescriptor = {
|
type DeviceDescriptor = {
|
||||||
|
|
@ -31,6 +35,8 @@ type Devices = { [name: string]: DeviceDescriptor };
|
||||||
|
|
||||||
export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
|
export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
|
||||||
readonly devices: Devices;
|
readonly devices: Devices;
|
||||||
|
private _harBackends = new Map<string, HarBackend>();
|
||||||
|
private _stackSessions = new Map<string, localUtils.StackSession>();
|
||||||
|
|
||||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.LocalUtilsInitializer) {
|
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.LocalUtilsInitializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
|
|
@ -39,4 +45,134 @@ export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
|
||||||
for (const { name, descriptor } of initializer.deviceDescriptors)
|
for (const { name, descriptor } of initializer.deviceDescriptors)
|
||||||
this.devices[name] = descriptor;
|
this.devices[name] = descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async zip(params: channels.LocalUtilsZipParams): Promise<void> {
|
||||||
|
return await localUtils.zip(this._platform, this._stackSessions, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async harOpen(params: channels.LocalUtilsHarOpenParams): Promise<channels.LocalUtilsHarOpenResult> {
|
||||||
|
return await localUtils.harOpen(this._harBackends, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async harLookup(params: channels.LocalUtilsHarLookupParams): Promise<channels.LocalUtilsHarLookupResult> {
|
||||||
|
return await localUtils.harLookup(this._harBackends, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async harClose(params: channels.LocalUtilsHarCloseParams): Promise<void> {
|
||||||
|
return await localUtils.harClose(this._harBackends, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async harUnzip(params: channels.LocalUtilsHarUnzipParams): Promise<void> {
|
||||||
|
return await localUtils.harUnzip(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async tracingStarted(params: channels.LocalUtilsTracingStartedParams): Promise<channels.LocalUtilsTracingStartedResult> {
|
||||||
|
return await localUtils.tracingStarted(this._stackSessions, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams): Promise<void> {
|
||||||
|
return await localUtils.traceDiscarded(this._platform, this._stackSessions, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> {
|
||||||
|
return await localUtils.addStackToTracingNoReply(this._stackSessions, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(params: channels.LocalUtilsConnectParams): Promise<Connection> {
|
||||||
|
const transport = this._platform.ws ? new WebSocketTransport(this._platform) : new JsonPipeTransport(this);
|
||||||
|
const connectHeaders = await transport.connect(params);
|
||||||
|
const connection = new Connection(this, this._platform, this._instrumentation, connectHeaders);
|
||||||
|
connection.markAsRemote();
|
||||||
|
connection.on('close', () => transport.close());
|
||||||
|
|
||||||
|
let closeError: string | undefined;
|
||||||
|
const onTransportClosed = (reason?: string) => {
|
||||||
|
connection.close(reason || closeError);
|
||||||
|
};
|
||||||
|
transport.onClose(reason => onTransportClosed(reason));
|
||||||
|
connection.onmessage = message => transport.send(message).catch(() => onTransportClosed());
|
||||||
|
transport.onMessage(message => {
|
||||||
|
try {
|
||||||
|
connection!.dispatch(message);
|
||||||
|
} catch (e) {
|
||||||
|
closeError = String(e);
|
||||||
|
transport.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interface Transport {
|
||||||
|
connect(params: channels.LocalUtilsConnectParams): Promise<HeadersArray>;
|
||||||
|
send(message: any): Promise<void>;
|
||||||
|
onMessage(callback: (message: object) => void): void;
|
||||||
|
onClose(callback: (reason?: string) => void): void;
|
||||||
|
close(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
class JsonPipeTransport implements Transport {
|
||||||
|
private _pipe: channels.JsonPipeChannel | undefined;
|
||||||
|
private _owner: ChannelOwner<channels.LocalUtilsChannel>;
|
||||||
|
|
||||||
|
constructor(owner: ChannelOwner<channels.LocalUtilsChannel>) {
|
||||||
|
this._owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(params: channels.LocalUtilsConnectParams) {
|
||||||
|
const { pipe, headers: connectHeaders } = await this._owner._wrapApiCall(async () => {
|
||||||
|
return await this._owner._channel.connect(params);
|
||||||
|
}, /* isInternal */ true);
|
||||||
|
this._pipe = pipe;
|
||||||
|
return connectHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(message: object) {
|
||||||
|
this._owner._wrapApiCall(async () => {
|
||||||
|
await this._pipe!.send({ message });
|
||||||
|
}, /* isInternal */ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(callback: (message: object) => void) {
|
||||||
|
this._pipe!.on('message', ({ message }) => callback(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose(callback: (reason?: string) => void) {
|
||||||
|
this._pipe!.on('closed', ({ reason }) => callback(reason));
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
await this._owner._wrapApiCall(async () => {
|
||||||
|
await this._pipe!.close().catch(() => {});
|
||||||
|
}, /* isInternal */ true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketTransport implements Transport {
|
||||||
|
private _platform: Platform;
|
||||||
|
private _ws: WebSocket | undefined;
|
||||||
|
|
||||||
|
constructor(platform: Platform) {
|
||||||
|
this._platform = platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(params: channels.LocalUtilsConnectParams) {
|
||||||
|
this._ws = this._platform.ws!(params.wsEndpoint);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(message: object) {
|
||||||
|
this._ws!.send(JSON.stringify(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(callback: (message: object) => void) {
|
||||||
|
this._ws!.addEventListener('message', event => callback(JSON.parse(event.data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose(callback: (reason?: string) => void) {
|
||||||
|
this._ws!.addEventListener('close', () => callback());
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
this._ws!.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as util from 'util';
|
|
||||||
|
|
||||||
import { asLocator, isString, monotonicTime } from '../utils';
|
|
||||||
import { ElementHandle } from './elementHandle';
|
import { ElementHandle } from './elementHandle';
|
||||||
import { parseResult, serializeArgument } from './jsHandle';
|
import { parseResult, serializeArgument } from './jsHandle';
|
||||||
|
import { asLocator } from '../utils/isomorphic/locatorGenerators';
|
||||||
import { getByAltTextSelector, getByLabelSelector, getByPlaceholderSelector, getByRoleSelector, getByTestIdSelector, getByTextSelector, getByTitleSelector } from '../utils/isomorphic/locatorUtils';
|
import { getByAltTextSelector, getByLabelSelector, getByPlaceholderSelector, getByRoleSelector, getByTestIdSelector, getByTextSelector, getByTitleSelector } from '../utils/isomorphic/locatorUtils';
|
||||||
import { escapeForTextSelector } from '../utils/isomorphic/stringUtils';
|
import { escapeForTextSelector } from '../utils/isomorphic/stringUtils';
|
||||||
|
import { isString } from '../utils/rtti';
|
||||||
|
import { monotonicTime } from '../utils/time';
|
||||||
|
|
||||||
import type { Frame } from './frame';
|
import type { Frame } from './frame';
|
||||||
import type { FilePayload, FrameExpectParams, Rect, SelectOption, SelectOptionOptions, TimeoutOptions } from './types';
|
import type { FilePayload, FrameExpectParams, Rect, SelectOption, SelectOptionOptions, TimeoutOptions } from './types';
|
||||||
|
|
@ -64,6 +64,9 @@ export class Locator implements api.Locator {
|
||||||
throw new Error(`Inner "hasNot" locator must belong to the same frame.`);
|
throw new Error(`Inner "hasNot" locator must belong to the same frame.`);
|
||||||
this._selector += ` >> internal:has-not=` + JSON.stringify(locator._selector);
|
this._selector += ` >> internal:has-not=` + JSON.stringify(locator._selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._frame._platform.inspectCustom)
|
||||||
|
(this as any)[this._frame._platform.inspectCustom] = () => this._inspect();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _withElement<R>(task: (handle: ElementHandle<SVGElement | HTMLElement>, timeout?: number) => Promise<R>, timeout?: number): Promise<R> {
|
private async _withElement<R>(task: (handle: ElementHandle<SVGElement | HTMLElement>, timeout?: number) => Promise<R>, timeout?: number): Promise<R> {
|
||||||
|
|
@ -291,8 +294,9 @@ export class Locator implements api.Locator {
|
||||||
return await this._frame.press(this._selector, key, { strict: true, ...options });
|
return await this._frame.press(this._selector, key, { strict: true, ...options });
|
||||||
}
|
}
|
||||||
|
|
||||||
async screenshot(options: Omit<channels.ElementHandleScreenshotOptions, 'mask'> & { path?: string, mask?: Locator[] } = {}): Promise<Buffer> {
|
async screenshot(options: Omit<channels.ElementHandleScreenshotOptions, 'mask'> & { path?: string, mask?: api.Locator[] } = {}): Promise<Buffer> {
|
||||||
return await this._withElement((h, timeout) => h.screenshot({ ...options, timeout }), options.timeout);
|
const mask = options.mask as Locator[] | undefined;
|
||||||
|
return await this._withElement((h, timeout) => h.screenshot({ ...options, mask, timeout }), options.timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ariaSnapshot(options?: { _id?: boolean, _mode?: 'raw' | 'regex' } & TimeoutOptions): Promise<string> {
|
async ariaSnapshot(options?: { _id?: boolean, _mode?: 'raw' | 'regex' } & TimeoutOptions): Promise<string> {
|
||||||
|
|
@ -370,7 +374,7 @@ export class Locator implements api.Locator {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
[util.inspect.custom]() {
|
private _inspect() {
|
||||||
return this.toString();
|
return this.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import { URLSearchParams } from 'url';
|
import { URLSearchParams } from 'url';
|
||||||
|
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
|
|
@ -22,19 +21,26 @@ import { isTargetClosedError } from './errors';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { APIResponse } from './fetch';
|
import { APIResponse } from './fetch';
|
||||||
import { Frame } from './frame';
|
import { Frame } from './frame';
|
||||||
import { Worker } from './worker';
|
|
||||||
import { MultiMap, assert, headersObjectToArray, isRegExp, isString, rewriteErrorMessage, urlMatches, zones } from '../utils';
|
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
|
import { Worker } from './worker';
|
||||||
|
import { assert } from '../utils/debug';
|
||||||
|
import { headersObjectToArray } from '../utils/headers';
|
||||||
|
import { urlMatches } from '../utils/isomorphic/urlMatch';
|
||||||
import { LongStandingScope, ManualPromise } from '../utils/manualPromise';
|
import { LongStandingScope, ManualPromise } from '../utils/manualPromise';
|
||||||
|
import { MultiMap } from '../utils/multimap';
|
||||||
|
import { isRegExp, isString } from '../utils/rtti';
|
||||||
|
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||||
|
import { zones } from '../utils/zones';
|
||||||
import { mime } from '../utilsBundle';
|
import { mime } from '../utilsBundle';
|
||||||
|
|
||||||
import type { Headers, RemoteAddr, SecurityDetails, WaitForEventOptions } from './types';
|
|
||||||
import type { URLMatch, Zone } from '../utils';
|
|
||||||
import type { BrowserContext } from './browserContext';
|
import type { BrowserContext } from './browserContext';
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
|
import type { Headers, RemoteAddr, SecurityDetails, WaitForEventOptions } from './types';
|
||||||
import type { Serializable } from '../../types/structs';
|
import type { Serializable } from '../../types/structs';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type { HeadersArray } from '../common/types';
|
import type { HeadersArray } from '../common/types';
|
||||||
|
import type { URLMatch } from '../utils/isomorphic/urlMatch';
|
||||||
|
import type { Zone } from '../utils/zones';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
export type NetworkCookie = {
|
export type NetworkCookie = {
|
||||||
|
|
@ -387,7 +393,7 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
|
||||||
let isBase64 = false;
|
let isBase64 = false;
|
||||||
let length = 0;
|
let length = 0;
|
||||||
if (options.path) {
|
if (options.path) {
|
||||||
const buffer = await fs.promises.readFile(options.path);
|
const buffer = await this._platform.fs().promises.readFile(options.path);
|
||||||
body = buffer.toString('base64');
|
body = buffer.toString('base64');
|
||||||
isBase64 = true;
|
isBase64 = true;
|
||||||
length = buffer.length;
|
length = buffer.length;
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
import { TargetClosedError, isTargetClosedError, serializeError } from './errors';
|
|
||||||
import { TimeoutSettings } from '../common/timeoutSettings';
|
|
||||||
import { LongStandingScope, assert, headersObjectToArray, isObject, isRegExp, isString, mkdirIfNeeded, trimStringWithEllipsis, urlMatches, urlMatchesEqual } from '../utils';
|
|
||||||
import { Accessibility } from './accessibility';
|
import { Accessibility } from './accessibility';
|
||||||
import { Artifact } from './artifact';
|
import { Artifact } from './artifact';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
|
|
@ -28,16 +22,25 @@ import { evaluationScript } from './clientHelper';
|
||||||
import { Coverage } from './coverage';
|
import { Coverage } from './coverage';
|
||||||
import { Download } from './download';
|
import { Download } from './download';
|
||||||
import { ElementHandle, determineScreenshotType } from './elementHandle';
|
import { ElementHandle, determineScreenshotType } from './elementHandle';
|
||||||
|
import { TargetClosedError, isTargetClosedError, serializeError } from './errors';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { FileChooser } from './fileChooser';
|
import { FileChooser } from './fileChooser';
|
||||||
import { Frame, verifyLoadState } from './frame';
|
import { Frame, verifyLoadState } from './frame';
|
||||||
import { HarRouter } from './harRouter';
|
import { HarRouter } from './harRouter';
|
||||||
import { Keyboard, Mouse, Touchscreen } from './input';
|
import { Keyboard, Mouse, Touchscreen } from './input';
|
||||||
import { JSHandle, assertMaxArguments, parseResult, serializeArgument } from './jsHandle';
|
import { JSHandle, assertMaxArguments, parseResult, serializeArgument } from './jsHandle';
|
||||||
import { Response, Route, RouteHandler, WebSocket, WebSocketRoute, WebSocketRouteHandler, validateHeaders } from './network';
|
import { Response, Route, RouteHandler, WebSocket, WebSocketRoute, WebSocketRouteHandler, validateHeaders } from './network';
|
||||||
import { Video } from './video';
|
import { Video } from './video';
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
import { Worker } from './worker';
|
import { Worker } from './worker';
|
||||||
|
import { TimeoutSettings } from '../common/timeoutSettings';
|
||||||
|
import { assert } from '../utils/debug';
|
||||||
|
import { mkdirIfNeeded } from '../utils/fileUtils';
|
||||||
|
import { headersObjectToArray } from '../utils/headers';
|
||||||
|
import { trimStringWithEllipsis } from '../utils/isomorphic/stringUtils';
|
||||||
|
import { urlMatches, urlMatchesEqual } from '../utils/isomorphic/urlMatch';
|
||||||
|
import { LongStandingScope } from '../utils/manualPromise';
|
||||||
|
import { isObject, isRegExp, isString } from '../utils/rtti';
|
||||||
|
|
||||||
import type { BrowserContext } from './browserContext';
|
import type { BrowserContext } from './browserContext';
|
||||||
import type { Clock } from './clock';
|
import type { Clock } from './clock';
|
||||||
|
|
@ -48,8 +51,8 @@ import type { Request, RouteHandlerCallback, WebSocketRouteHandlerCallback } fro
|
||||||
import type { FilePayload, Headers, LifecycleEvent, SelectOption, SelectOptionOptions, Size, WaitForEventOptions, WaitForFunctionOptions } from './types';
|
import type { FilePayload, Headers, LifecycleEvent, SelectOption, SelectOptionOptions, Size, WaitForEventOptions, WaitForFunctionOptions } from './types';
|
||||||
import type * as structs from '../../types/structs';
|
import type * as structs from '../../types/structs';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
import type { URLMatch } from '../utils';
|
|
||||||
import type { ByRoleOptions } from '../utils/isomorphic/locatorUtils';
|
import type { ByRoleOptions } from '../utils/isomorphic/locatorUtils';
|
||||||
|
import type { URLMatch } from '../utils/isomorphic/urlMatch';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
type PDFOptions = Omit<channels.PagePdfParams, 'width' | 'height' | 'margin'> & {
|
type PDFOptions = Omit<channels.PagePdfParams, 'width' | 'height' | 'margin'> & {
|
||||||
|
|
@ -512,7 +515,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||||
}
|
}
|
||||||
|
|
||||||
async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) {
|
async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) {
|
||||||
const source = await evaluationScript(script, arg);
|
const source = await evaluationScript(this._platform, script, arg);
|
||||||
await this._channel.addInitScript({ source });
|
await this._channel.addInitScript({ source });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -590,8 +593,8 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||||
}
|
}
|
||||||
const result = await this._channel.screenshot(copy);
|
const result = await this._channel.screenshot(copy);
|
||||||
if (options.path) {
|
if (options.path) {
|
||||||
await mkdirIfNeeded(options.path);
|
await mkdirIfNeeded(this._platform, options.path);
|
||||||
await fs.promises.writeFile(options.path, result.binary);
|
await this._platform.fs().promises.writeFile(options.path, result.binary);
|
||||||
}
|
}
|
||||||
return result.binary;
|
return result.binary;
|
||||||
}
|
}
|
||||||
|
|
@ -820,8 +823,9 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||||
}
|
}
|
||||||
const result = await this._channel.pdf(transportOptions);
|
const result = await this._channel.pdf(transportOptions);
|
||||||
if (options.path) {
|
if (options.path) {
|
||||||
await fs.promises.mkdir(path.dirname(options.path), { recursive: true });
|
const platform = this._platform;
|
||||||
await fs.promises.writeFile(options.path, result.pdf);
|
await platform.fs().promises.mkdir(platform.path().dirname(options.path), { recursive: true });
|
||||||
|
await platform.fs().promises.writeFile(options.path, result.pdf);
|
||||||
}
|
}
|
||||||
return result.pdf;
|
return result.pdf;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { evaluationScript } from './clientHelper';
|
import { evaluationScript } from './clientHelper';
|
||||||
import { setTestIdAttribute, testIdAttributeName } from './locator';
|
import { setTestIdAttribute, testIdAttributeName } from './locator';
|
||||||
|
import { nodePlatform } from '../utils/platform';
|
||||||
|
|
||||||
import type { SelectorEngine } from './types';
|
import type { SelectorEngine } from './types';
|
||||||
import type * as api from '../../types/types';
|
import type * as api from '../../types/types';
|
||||||
|
|
@ -28,7 +29,7 @@ export class Selectors implements api.Selectors {
|
||||||
private _registrations: channels.SelectorsRegisterParams[] = [];
|
private _registrations: channels.SelectorsRegisterParams[] = [];
|
||||||
|
|
||||||
async register(name: string, script: string | (() => SelectorEngine) | { path?: string, content?: string }, options: { contentScript?: boolean } = {}): Promise<void> {
|
async register(name: string, script: string | (() => SelectorEngine) | { path?: string, content?: string }, options: { contentScript?: boolean } = {}): Promise<void> {
|
||||||
const source = await evaluationScript(script, undefined, false);
|
const source = await evaluationScript(nodePlatform, script, undefined, false);
|
||||||
const params = { ...options, name, source };
|
const params = { ...options, name, source };
|
||||||
for (const channel of this._channels)
|
for (const channel of this._channels)
|
||||||
await channel._channel.register(params);
|
await channel._channel.register(params);
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
||||||
this._isTracing = true;
|
this._isTracing = true;
|
||||||
this._connection.setIsTracing(true);
|
this._connection.setIsTracing(true);
|
||||||
}
|
}
|
||||||
const result = await this._connection.localUtils()._channel.tracingStarted({ tracesDir: this._tracesDir, traceName });
|
const result = await this._connection.localUtils().tracingStarted({ tracesDir: this._tracesDir, traceName });
|
||||||
this._stacksId = result.stacksId;
|
this._stacksId = result.stacksId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,7 +89,7 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
||||||
// Not interested in artifacts.
|
// Not interested in artifacts.
|
||||||
await this._channel.tracingStopChunk({ mode: 'discard' });
|
await this._channel.tracingStopChunk({ mode: 'discard' });
|
||||||
if (this._stacksId)
|
if (this._stacksId)
|
||||||
await this._connection.localUtils()._channel.traceDiscarded({ stacksId: this._stacksId });
|
await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +97,7 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
||||||
|
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
const result = await this._channel.tracingStopChunk({ mode: 'entries' });
|
const result = await this._channel.tracingStopChunk({ mode: 'entries' });
|
||||||
await this._connection.localUtils()._channel.zip({ zipFile: filePath, entries: result.entries!, mode: 'write', stacksId: this._stacksId, includeSources: this._includeSources });
|
await this._connection.localUtils().zip({ zipFile: filePath, entries: result.entries!, mode: 'write', stacksId: this._stacksId, includeSources: this._includeSources });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
||||||
// The artifact may be missing if the browser closed while stopping tracing.
|
// The artifact may be missing if the browser closed while stopping tracing.
|
||||||
if (!result.artifact) {
|
if (!result.artifact) {
|
||||||
if (this._stacksId)
|
if (this._stacksId)
|
||||||
await this._connection.localUtils()._channel.traceDiscarded({ stacksId: this._stacksId });
|
await this._connection.localUtils().traceDiscarded({ stacksId: this._stacksId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
||||||
await artifact.saveAs(filePath);
|
await artifact.saveAs(filePath);
|
||||||
await artifact.delete();
|
await artifact.delete();
|
||||||
|
|
||||||
await this._connection.localUtils()._channel.zip({ zipFile: filePath, entries: [], mode: 'append', stacksId: this._stacksId, includeSources: this._includeSources });
|
await this._connection.localUtils().zip({ zipFile: filePath, entries: [], mode: 'append', stacksId: this._stacksId, includeSources: this._includeSources });
|
||||||
}
|
}
|
||||||
|
|
||||||
_resetStackCounter() {
|
_resetStackCounter() {
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export type SelectOptionOptions = { force?: boolean, timeout?: number };
|
||||||
export type FilePayload = { name: string, mimeType: string, buffer: Buffer };
|
export type FilePayload = { name: string, mimeType: string, buffer: Buffer };
|
||||||
export type StorageState = {
|
export type StorageState = {
|
||||||
cookies: channels.NetworkCookie[],
|
cookies: channels.NetworkCookie[],
|
||||||
origins: channels.OriginStorage[]
|
origins: channels.OriginStorage[],
|
||||||
};
|
};
|
||||||
export type SetStorageState = {
|
export type SetStorageState = {
|
||||||
cookies?: channels.SetNetworkCookie[],
|
cookies?: channels.SetNetworkCookie[],
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ManualPromise } from '../utils';
|
import { ManualPromise } from '../utils/manualPromise';
|
||||||
|
|
||||||
import type { Artifact } from './artifact';
|
import type { Artifact } from './artifact';
|
||||||
import type { Connection } from './connection';
|
import type { Connection } from './connection';
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { TimeoutError } from './errors';
|
import { TimeoutError } from './errors';
|
||||||
import { createGuid, zones } from '../utils';
|
import { createGuid } from '../utils/crypto';
|
||||||
import { rewriteErrorMessage } from '../utils/stackTrace';
|
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||||
|
import { zones } from '../utils/zones';
|
||||||
|
|
||||||
import type { Zone } from '../utils';
|
|
||||||
import type { ChannelOwner } from './channelOwner';
|
import type { ChannelOwner } from './channelOwner';
|
||||||
|
import type { Zone } from '../utils/zones';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import type { EventEmitter } from 'events';
|
import type { EventEmitter } from 'events';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
|
import { TargetClosedError } from './errors';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { JSHandle, assertMaxArguments, parseResult, serializeArgument } from './jsHandle';
|
import { JSHandle, assertMaxArguments, parseResult, serializeArgument } from './jsHandle';
|
||||||
import { LongStandingScope } from '../utils';
|
import { LongStandingScope } from '../utils/manualPromise';
|
||||||
import { TargetClosedError } from './errors';
|
|
||||||
|
|
||||||
import type { BrowserContext } from './browserContext';
|
import type { BrowserContext } from './browserContext';
|
||||||
import type { Page } from './page';
|
import type { Page } from './page';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
[*]
|
[*]
|
||||||
../utils/
|
../utils/
|
||||||
../utilsBundle.ts
|
../utilsBundle.ts
|
||||||
|
../zipBundle.ts
|
||||||
|
|
|
||||||
23
packages/playwright-core/src/common/progress.ts
Normal file
23
packages/playwright-core/src/common/progress.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Progress {
|
||||||
|
log(message: string): void;
|
||||||
|
timeUntilDeadline(): number;
|
||||||
|
isRunning(): boolean;
|
||||||
|
cleanupWhenAborted(cleanup: () => any): void;
|
||||||
|
throwIfAborted(): void;
|
||||||
|
}
|
||||||
|
|
@ -21,11 +21,12 @@ import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlayw
|
||||||
|
|
||||||
import type { Playwright as PlaywrightAPI } from './client/playwright';
|
import type { Playwright as PlaywrightAPI } from './client/playwright';
|
||||||
import type { Language } from './utils';
|
import type { Language } from './utils';
|
||||||
|
import type { Platform } from './utils/platform';
|
||||||
|
|
||||||
export function createInProcessPlaywright(): PlaywrightAPI {
|
export function createInProcessPlaywright(platform: Platform): PlaywrightAPI {
|
||||||
const playwright = createPlaywright({ sdkLanguage: (process.env.PW_LANG_NAME as Language | undefined) || 'javascript' });
|
const playwright = createPlaywright({ sdkLanguage: (process.env.PW_LANG_NAME as Language | undefined) || 'javascript' });
|
||||||
|
|
||||||
const clientConnection = new Connection(undefined, undefined);
|
const clientConnection = new Connection(undefined, platform, undefined, []);
|
||||||
clientConnection.useRawBuffers();
|
clientConnection.useRawBuffers();
|
||||||
const dispatcherConnection = new DispatcherConnection(true /* local */);
|
const dispatcherConnection = new DispatcherConnection(true /* local */);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createInProcessPlaywright } from './inProcessFactory';
|
import { createInProcessPlaywright } from './inProcessFactory';
|
||||||
|
import { nodePlatform } from './utils/platform';
|
||||||
|
|
||||||
module.exports = createInProcessPlaywright();
|
module.exports = createInProcessPlaywright(nodePlatform);
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@ import * as path from 'path';
|
||||||
import { Connection } from './client/connection';
|
import { Connection } from './client/connection';
|
||||||
import { PipeTransport } from './protocol/transport';
|
import { PipeTransport } from './protocol/transport';
|
||||||
import { ManualPromise } from './utils/manualPromise';
|
import { ManualPromise } from './utils/manualPromise';
|
||||||
|
import { nodePlatform } from './utils/platform';
|
||||||
|
|
||||||
import type { Playwright } from './client/playwright';
|
import type { Playwright } from './client/playwright';
|
||||||
|
|
||||||
|
|
||||||
export async function start(env: any = {}): Promise<{ playwright: Playwright, stop: () => Promise<void> }> {
|
export async function start(env: any = {}): Promise<{ playwright: Playwright, stop: () => Promise<void> }> {
|
||||||
const client = new PlaywrightClient(env);
|
const client = new PlaywrightClient(env);
|
||||||
const playwright = await client._playwright;
|
const playwright = await client._playwright;
|
||||||
|
|
@ -48,7 +48,7 @@ class PlaywrightClient {
|
||||||
this._driverProcess.unref();
|
this._driverProcess.unref();
|
||||||
this._driverProcess.stderr!.on('data', data => process.stderr.write(data));
|
this._driverProcess.stderr!.on('data', data => process.stderr.write(data));
|
||||||
|
|
||||||
const connection = new Connection(undefined, undefined);
|
const connection = new Connection(undefined, nodePlatform, undefined, []);
|
||||||
const transport = new PipeTransport(this._driverProcess.stdin!, this._driverProcess.stdout!);
|
const transport = new PipeTransport(this._driverProcess.stdin!, this._driverProcess.stdout!);
|
||||||
connection.onmessage = message => transport.send(JSON.stringify(message));
|
connection.onmessage = message => transport.send(JSON.stringify(message));
|
||||||
transport.onmessage = message => connection.dispatch(JSON.parse(message));
|
transport.onmessage = message => connection.dispatch(JSON.parse(message));
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,15 @@ import { TimeoutSettings } from '../../common/timeoutSettings';
|
||||||
import { PipeTransport } from '../../protocol/transport';
|
import { PipeTransport } from '../../protocol/transport';
|
||||||
import { createGuid, getPackageManagerExecCommand, isUnderTest, makeWaitForNextTask } from '../../utils';
|
import { createGuid, getPackageManagerExecCommand, isUnderTest, makeWaitForNextTask } from '../../utils';
|
||||||
import { RecentLogsCollector } from '../../utils/debugLogger';
|
import { RecentLogsCollector } from '../../utils/debugLogger';
|
||||||
import { removeFolders } from '../../utils/fileUtils';
|
|
||||||
import { gracefullyCloseSet } from '../../utils/processLauncher';
|
|
||||||
import { debug } from '../../utilsBundle';
|
import { debug } from '../../utilsBundle';
|
||||||
import { wsReceiver, wsSender } from '../../utilsBundle';
|
import { wsReceiver, wsSender } from '../../utilsBundle';
|
||||||
import { validateBrowserContextOptions } from '../browserContext';
|
import { validateBrowserContextOptions } from '../browserContext';
|
||||||
import { chromiumSwitches } from '../chromium/chromiumSwitches';
|
import { chromiumSwitches } from '../chromium/chromiumSwitches';
|
||||||
import { CRBrowser } from '../chromium/crBrowser';
|
import { CRBrowser } from '../chromium/crBrowser';
|
||||||
|
import { removeFolders } from '../fileUtils';
|
||||||
import { helper } from '../helper';
|
import { helper } from '../helper';
|
||||||
import { SdkObject, serverSideCallMetadata } from '../instrumentation';
|
import { SdkObject, serverSideCallMetadata } from '../instrumentation';
|
||||||
|
import { gracefullyCloseSet } from '../processLauncher';
|
||||||
import { ProgressController } from '../progress';
|
import { ProgressController } from '../progress';
|
||||||
import { registry } from '../registry';
|
import { registry } from '../registry';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ import { BidiBrowser } from './bidiBrowser';
|
||||||
import { kBrowserCloseMessageId } from './bidiConnection';
|
import { kBrowserCloseMessageId } from './bidiConnection';
|
||||||
import { chromiumSwitches } from '../chromium/chromiumSwitches';
|
import { chromiumSwitches } from '../chromium/chromiumSwitches';
|
||||||
|
|
||||||
import type { Env } from '../../utils/processLauncher';
|
|
||||||
import type { BrowserOptions } from '../browser';
|
import type { BrowserOptions } from '../browser';
|
||||||
import type { SdkObject } from '../instrumentation';
|
import type { SdkObject } from '../instrumentation';
|
||||||
|
import type { Env } from '../processLauncher';
|
||||||
import type { ProtocolError } from '../protocolError';
|
import type { ProtocolError } from '../protocolError';
|
||||||
import type { ConnectionTransport } from '../transport';
|
import type { ConnectionTransport } from '../transport';
|
||||||
import type * as types from '../types';
|
import type * as types from '../types';
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ import { BidiBrowser } from './bidiBrowser';
|
||||||
import { kBrowserCloseMessageId } from './bidiConnection';
|
import { kBrowserCloseMessageId } from './bidiConnection';
|
||||||
import { createProfile } from './third_party/firefoxPrefs';
|
import { createProfile } from './third_party/firefoxPrefs';
|
||||||
|
|
||||||
import type { Env } from '../../utils/processLauncher';
|
|
||||||
import type { BrowserOptions } from '../browser';
|
import type { BrowserOptions } from '../browser';
|
||||||
import type { SdkObject } from '../instrumentation';
|
import type { SdkObject } from '../instrumentation';
|
||||||
|
import type { Env } from '../processLauncher';
|
||||||
import type { ProtocolError } from '../protocolError';
|
import type { ProtocolError } from '../protocolError';
|
||||||
import type { ConnectionTransport } from '../transport';
|
import type { ConnectionTransport } from '../transport';
|
||||||
import type * as types from '../types';
|
import type * as types from '../types';
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { createGuid, debugMode } from '../utils';
|
||||||
import { Clock } from './clock';
|
import { Clock } from './clock';
|
||||||
import { Debugger } from './debugger';
|
import { Debugger } from './debugger';
|
||||||
import { BrowserContextAPIRequestContext } from './fetch';
|
import { BrowserContextAPIRequestContext } from './fetch';
|
||||||
|
import { mkdirIfNeeded } from './fileUtils';
|
||||||
import { HarRecorder } from './har/harRecorder';
|
import { HarRecorder } from './har/harRecorder';
|
||||||
import { helper } from './helper';
|
import { helper } from './helper';
|
||||||
import { SdkObject, serverSideCallMetadata } from './instrumentation';
|
import { SdkObject, serverSideCallMetadata } from './instrumentation';
|
||||||
|
|
@ -31,9 +32,8 @@ import * as network from './network';
|
||||||
import { InitScript } from './page';
|
import { InitScript } from './page';
|
||||||
import { Page, PageBinding } from './page';
|
import { Page, PageBinding } from './page';
|
||||||
import { Recorder } from './recorder';
|
import { Recorder } from './recorder';
|
||||||
import * as storageScript from './storageScript';
|
|
||||||
import { mkdirIfNeeded } from '../utils/fileUtils';
|
|
||||||
import { RecorderApp } from './recorder/recorderApp';
|
import { RecorderApp } from './recorder/recorderApp';
|
||||||
|
import * as storageScript from './storageScript';
|
||||||
import * as consoleApiSource from '../generated/consoleApiSource';
|
import * as consoleApiSource from '../generated/consoleApiSource';
|
||||||
import { Tracing } from './trace/recorder/tracing';
|
import { Tracing } from './trace/recorder/tracing';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,28 +20,28 @@ import * as path from 'path';
|
||||||
|
|
||||||
import { normalizeProxySettings, validateBrowserContextOptions } from './browserContext';
|
import { normalizeProxySettings, validateBrowserContextOptions } from './browserContext';
|
||||||
import { DEFAULT_TIMEOUT, TimeoutSettings } from '../common/timeoutSettings';
|
import { DEFAULT_TIMEOUT, TimeoutSettings } from '../common/timeoutSettings';
|
||||||
import { ManualPromise, debugMode } from '../utils';
|
import { ManualPromise, assert, debugMode } from '../utils';
|
||||||
|
import { existsAsync } from './fileUtils';
|
||||||
import { helper } from './helper';
|
import { helper } from './helper';
|
||||||
import { SdkObject } from './instrumentation';
|
import { SdkObject } from './instrumentation';
|
||||||
import { PipeTransport } from './pipeTransport';
|
import { PipeTransport } from './pipeTransport';
|
||||||
|
import { envArrayToObject, launchProcess } from './processLauncher';
|
||||||
import { ProgressController } from './progress';
|
import { ProgressController } from './progress';
|
||||||
import { isProtocolError } from './protocolError';
|
import { isProtocolError } from './protocolError';
|
||||||
import { registry } from './registry';
|
import { registry } from './registry';
|
||||||
import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor';
|
import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor';
|
||||||
import { WebSocketTransport } from './transport';
|
import { WebSocketTransport } from './transport';
|
||||||
import { RecentLogsCollector } from '../utils/debugLogger';
|
import { RecentLogsCollector } from '../utils/debugLogger';
|
||||||
import { existsAsync } from '../utils/fileUtils';
|
|
||||||
import { envArrayToObject, launchProcess } from '../utils/processLauncher';
|
|
||||||
|
|
||||||
import type { Browser, BrowserOptions, BrowserProcess } from './browser';
|
import type { Browser, BrowserOptions, BrowserProcess } from './browser';
|
||||||
import type { BrowserContext } from './browserContext';
|
import type { BrowserContext } from './browserContext';
|
||||||
import type { CallMetadata } from './instrumentation';
|
import type { CallMetadata } from './instrumentation';
|
||||||
|
import type { Env } from './processLauncher';
|
||||||
import type { Progress } from './progress';
|
import type { Progress } from './progress';
|
||||||
import type { ProtocolError } from './protocolError';
|
import type { ProtocolError } from './protocolError';
|
||||||
import type { BrowserName } from './registry';
|
import type { BrowserName } from './registry';
|
||||||
import type { ConnectionTransport } from './transport';
|
import type { ConnectionTransport } from './transport';
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
import type { Env } from '../utils/processLauncher';
|
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
export const kNoXServerRunningError = 'Looks like you launched a headed browser without having a XServer running.\n' +
|
export const kNoXServerRunningError = 'Looks like you launched a headed browser without having a XServer running.\n' +
|
||||||
|
|
@ -188,6 +188,7 @@ export abstract class BrowserType extends SdkObject {
|
||||||
tempDirectories.push(artifactsDir);
|
tempDirectories.push(artifactsDir);
|
||||||
|
|
||||||
if (userDataDir) {
|
if (userDataDir) {
|
||||||
|
assert(path.isAbsolute(userDataDir), 'userDataDir must be an absolute path');
|
||||||
// Firefox bails if the profile directory does not exist, Chrome creates it. We ensure consistent behavior here.
|
// Firefox bails if the profile directory does not exist, Chrome creates it. We ensure consistent behavior here.
|
||||||
if (!await existsAsync(userDataDir))
|
if (!await existsAsync(userDataDir))
|
||||||
await fs.promises.mkdir(userDataDir, { recursive: true, mode: 0o700 });
|
await fs.promises.mkdir(userDataDir, { recursive: true, mode: 0o700 });
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,8 @@ import { TimeoutSettings } from '../../common/timeoutSettings';
|
||||||
import { debugMode, headersArrayToObject, headersObjectToArray, } from '../../utils';
|
import { debugMode, headersArrayToObject, headersObjectToArray, } from '../../utils';
|
||||||
import { wrapInASCIIBox } from '../../utils/ascii';
|
import { wrapInASCIIBox } from '../../utils/ascii';
|
||||||
import { RecentLogsCollector } from '../../utils/debugLogger';
|
import { RecentLogsCollector } from '../../utils/debugLogger';
|
||||||
import { removeFolders } from '../../utils/fileUtils';
|
|
||||||
import { ManualPromise } from '../../utils/manualPromise';
|
import { ManualPromise } from '../../utils/manualPromise';
|
||||||
import { fetchData } from '../../utils/network';
|
import { fetchData } from '../../utils/network';
|
||||||
import { gracefullyCloseSet } from '../../utils/processLauncher';
|
|
||||||
import { getUserAgent } from '../../utils/userAgent';
|
import { getUserAgent } from '../../utils/userAgent';
|
||||||
import { validateBrowserContextOptions } from '../browserContext';
|
import { validateBrowserContextOptions } from '../browserContext';
|
||||||
import { BrowserType, kNoXServerRunningError } from '../browserType';
|
import { BrowserType, kNoXServerRunningError } from '../browserType';
|
||||||
|
|
@ -39,12 +37,14 @@ import { registry } from '../registry';
|
||||||
import { WebSocketTransport } from '../transport';
|
import { WebSocketTransport } from '../transport';
|
||||||
import { CRDevTools } from './crDevTools';
|
import { CRDevTools } from './crDevTools';
|
||||||
import { Browser } from '../browser';
|
import { Browser } from '../browser';
|
||||||
|
import { removeFolders } from '../fileUtils';
|
||||||
|
import { gracefullyCloseSet } from '../processLauncher';
|
||||||
import { ProgressController } from '../progress';
|
import { ProgressController } from '../progress';
|
||||||
|
|
||||||
import type { HTTPRequestParams } from '../../utils/network';
|
import type { HTTPRequestParams } from '../../utils/network';
|
||||||
import type { Env } from '../../utils/processLauncher';
|
|
||||||
import type { BrowserOptions, BrowserProcess } from '../browser';
|
import type { BrowserOptions, BrowserProcess } from '../browser';
|
||||||
import type { CallMetadata, SdkObject } from '../instrumentation';
|
import type { CallMetadata, SdkObject } from '../instrumentation';
|
||||||
|
import type { Env } from '../processLauncher';
|
||||||
import type { Progress } from '../progress';
|
import type { Progress } from '../progress';
|
||||||
import type { ProtocolError } from '../protocolError';
|
import type { ProtocolError } from '../protocolError';
|
||||||
import type { ConnectionTransport, ProtocolRequest } from '../transport';
|
import type { ConnectionTransport, ProtocolRequest } from '../transport';
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
|
||||||
import { mkdirIfNeeded } from '../../utils/fileUtils';
|
|
||||||
import { splitErrorMessage } from '../../utils/stackTrace';
|
import { splitErrorMessage } from '../../utils/stackTrace';
|
||||||
|
import { mkdirIfNeeded } from '../fileUtils';
|
||||||
|
|
||||||
import type { CRSession } from './crConnection';
|
import type { CRSession } from './crConnection';
|
||||||
import type { Protocol } from './protocol';
|
import type { Protocol } from './protocol';
|
||||||
|
|
|
||||||
|
|
@ -952,7 +952,7 @@ Should be updated alongside RequestIdTokenStatus in
|
||||||
third_party/blink/public/mojom/devtools/inspector_issue.mojom to include
|
third_party/blink/public/mojom/devtools/inspector_issue.mojom to include
|
||||||
all cases except for success.
|
all cases except for success.
|
||||||
*/
|
*/
|
||||||
export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"IdpNotPotentiallyTrustworthy"|"DisabledInSettings"|"DisabledInFlags"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenIdpErrorResponse"|"IdTokenCrossSiteIdpErrorResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"|"MissingTransientUserActivation"|"ReplacedByActiveMode"|"InvalidFieldsSpecified"|"RelyingPartyOriginIsOpaque"|"TypeNotMatching";
|
export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"IdpNotPotentiallyTrustworthy"|"DisabledInSettings"|"DisabledInFlags"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenIdpErrorResponse"|"IdTokenCrossSiteIdpErrorResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"|"MissingTransientUserActivation"|"ReplacedByActiveMode"|"InvalidFieldsSpecified"|"RelyingPartyOriginIsOpaque"|"TypeNotMatching"|"UiDismissedNoEmbargo";
|
||||||
export interface FederatedAuthUserInfoRequestIssueDetails {
|
export interface FederatedAuthUserInfoRequestIssueDetails {
|
||||||
federatedAuthUserInfoRequestIssueReason: FederatedAuthUserInfoRequestIssueReason;
|
federatedAuthUserInfoRequestIssueReason: FederatedAuthUserInfoRequestIssueReason;
|
||||||
}
|
}
|
||||||
|
|
@ -983,7 +983,7 @@ features, encourage the use of new ones, and provide general guidance.
|
||||||
}
|
}
|
||||||
export type SelectElementAccessibilityIssueReason = "DisallowedSelectChild"|"DisallowedOptGroupChild"|"NonPhrasingContentOptionChild"|"InteractiveContentOptionChild"|"InteractiveContentLegendChild";
|
export type SelectElementAccessibilityIssueReason = "DisallowedSelectChild"|"DisallowedOptGroupChild"|"NonPhrasingContentOptionChild"|"InteractiveContentOptionChild"|"InteractiveContentLegendChild";
|
||||||
/**
|
/**
|
||||||
* This isue warns about errors in the select element content model.
|
* This issue warns about errors in the select element content model.
|
||||||
*/
|
*/
|
||||||
export interface SelectElementAccessibilityIssueDetails {
|
export interface SelectElementAccessibilityIssueDetails {
|
||||||
nodeId: DOM.BackendNodeId;
|
nodeId: DOM.BackendNodeId;
|
||||||
|
|
@ -1187,6 +1187,19 @@ flag is set.
|
||||||
*/
|
*/
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Uninstalls an unpacked extension (others not supported) from the profile.
|
||||||
|
Available if the client is connected using the --remote-debugging-pipe flag
|
||||||
|
and the --enable-unsafe-extension-debugging.
|
||||||
|
*/
|
||||||
|
export type uninstallParameters = {
|
||||||
|
/**
|
||||||
|
* Extension id.
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
export type uninstallReturnValue = {
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Gets data from extension storage in the given `storageArea`. If `keys` is
|
* Gets data from extension storage in the given `storageArea`. If `keys` is
|
||||||
specified, these are used to filter the result.
|
specified, these are used to filter the result.
|
||||||
|
|
@ -2913,6 +2926,13 @@ incorrect results if the declaration contains a var() for example.
|
||||||
* Identifier of the frame where "via-inspector" stylesheet should be created.
|
* Identifier of the frame where "via-inspector" stylesheet should be created.
|
||||||
*/
|
*/
|
||||||
frameId: Page.FrameId;
|
frameId: Page.FrameId;
|
||||||
|
/**
|
||||||
|
* If true, creates a new stylesheet for every call. If false,
|
||||||
|
returns a stylesheet previously created by a call with force=false
|
||||||
|
for the frame's document if it exists or creates a new stylesheet
|
||||||
|
(default: false).
|
||||||
|
*/
|
||||||
|
force?: boolean;
|
||||||
}
|
}
|
||||||
export type createStyleSheetReturnValue = {
|
export type createStyleSheetReturnValue = {
|
||||||
/**
|
/**
|
||||||
|
|
@ -5974,81 +5994,6 @@ The final text color opacity is computed based on the opacity of all overlapping
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export module Database {
|
|
||||||
/**
|
|
||||||
* Unique identifier of Database object.
|
|
||||||
*/
|
|
||||||
export type DatabaseId = string;
|
|
||||||
/**
|
|
||||||
* Database object.
|
|
||||||
*/
|
|
||||||
export interface Database {
|
|
||||||
/**
|
|
||||||
* Database ID.
|
|
||||||
*/
|
|
||||||
id: DatabaseId;
|
|
||||||
/**
|
|
||||||
* Database domain.
|
|
||||||
*/
|
|
||||||
domain: string;
|
|
||||||
/**
|
|
||||||
* Database name.
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* Database version.
|
|
||||||
*/
|
|
||||||
version: string;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Database error.
|
|
||||||
*/
|
|
||||||
export interface Error {
|
|
||||||
/**
|
|
||||||
* Error message.
|
|
||||||
*/
|
|
||||||
message: string;
|
|
||||||
/**
|
|
||||||
* Error code.
|
|
||||||
*/
|
|
||||||
code: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type addDatabasePayload = {
|
|
||||||
database: Database;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disables database tracking, prevents database events from being sent to the client.
|
|
||||||
*/
|
|
||||||
export type disableParameters = {
|
|
||||||
}
|
|
||||||
export type disableReturnValue = {
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Enables database tracking, database events will now be delivered to the client.
|
|
||||||
*/
|
|
||||||
export type enableParameters = {
|
|
||||||
}
|
|
||||||
export type enableReturnValue = {
|
|
||||||
}
|
|
||||||
export type executeSQLParameters = {
|
|
||||||
databaseId: DatabaseId;
|
|
||||||
query: string;
|
|
||||||
}
|
|
||||||
export type executeSQLReturnValue = {
|
|
||||||
columnNames?: string[];
|
|
||||||
values?: any[];
|
|
||||||
sqlError?: Error;
|
|
||||||
}
|
|
||||||
export type getDatabaseTableNamesParameters = {
|
|
||||||
databaseId: DatabaseId;
|
|
||||||
}
|
|
||||||
export type getDatabaseTableNamesReturnValue = {
|
|
||||||
tableNames: string[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export module DeviceOrientation {
|
export module DeviceOrientation {
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -9126,7 +9071,7 @@ This is a temporary ability and it will be removed in the future.
|
||||||
/**
|
/**
|
||||||
* Types of reasons why a cookie should have been blocked by 3PCD but is exempted for the request.
|
* Types of reasons why a cookie should have been blocked by 3PCD but is exempted for the request.
|
||||||
*/
|
*/
|
||||||
export type CookieExemptionReason = "None"|"UserSetting"|"TPCDMetadata"|"TPCDDeprecationTrial"|"TopLevelTPCDDeprecationTrial"|"TPCDHeuristics"|"EnterprisePolicy"|"StorageAccess"|"TopLevelStorageAccess"|"Scheme";
|
export type CookieExemptionReason = "None"|"UserSetting"|"TPCDMetadata"|"TPCDDeprecationTrial"|"TopLevelTPCDDeprecationTrial"|"TPCDHeuristics"|"EnterprisePolicy"|"StorageAccess"|"TopLevelStorageAccess"|"Scheme"|"SameSiteNoneCookiesInSandbox";
|
||||||
/**
|
/**
|
||||||
* A cookie which was not stored from a response with the corresponding reason.
|
* A cookie which was not stored from a response with the corresponding reason.
|
||||||
*/
|
*/
|
||||||
|
|
@ -11702,7 +11647,7 @@ as an ad.
|
||||||
* All Permissions Policy features. This enum should match the one defined
|
* All Permissions Policy features. This enum should match the one defined
|
||||||
in third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5.
|
in third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5.
|
||||||
*/
|
*/
|
||||||
export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"controlled-frame"|"cross-origin-isolated"|"deferred-fetch"|"deferred-fetch-minimal"|"digital-credentials-get"|"direct-sockets"|"direct-sockets-private"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"fenced-unpartitioned-storage-read"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"media-playback-while-not-visible"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"popins"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-app-installation"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking";
|
export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-high-entropy-values"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"controlled-frame"|"cross-origin-isolated"|"deferred-fetch"|"deferred-fetch-minimal"|"digital-credentials-get"|"direct-sockets"|"direct-sockets-private"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"fenced-unpartitioned-storage-read"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"media-playback-while-not-visible"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"popins"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-app-installation"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking";
|
||||||
/**
|
/**
|
||||||
* Reason for a permissions policy feature to be disabled.
|
* Reason for a permissions policy feature to be disabled.
|
||||||
*/
|
*/
|
||||||
|
|
@ -12431,6 +12376,33 @@ subtree is actually detached.
|
||||||
frame: Frame;
|
frame: Frame;
|
||||||
}
|
}
|
||||||
export type frameResizedPayload = void;
|
export type frameResizedPayload = void;
|
||||||
|
/**
|
||||||
|
* Fired when a navigation starts. This event is fired for both
|
||||||
|
renderer-initiated and browser-initiated navigations. For renderer-initiated
|
||||||
|
navigations, the event is fired after `frameRequestedNavigation`.
|
||||||
|
Navigation may still be cancelled after the event is issued. Multiple events
|
||||||
|
can be fired for a single navigation, for example, when a same-document
|
||||||
|
navigation becomes a cross-document navigation (such as in the case of a
|
||||||
|
frameset).
|
||||||
|
*/
|
||||||
|
export type frameStartedNavigatingPayload = {
|
||||||
|
/**
|
||||||
|
* ID of the frame that is being navigated.
|
||||||
|
*/
|
||||||
|
frameId: FrameId;
|
||||||
|
/**
|
||||||
|
* The URL the navigation started with. The final URL can be different.
|
||||||
|
*/
|
||||||
|
url: string;
|
||||||
|
/**
|
||||||
|
* Loader identifier. Even though it is present in case of same-document
|
||||||
|
navigation, the previously committed loaderId would not change unless
|
||||||
|
the navigation changes from a same-document to a cross-document
|
||||||
|
navigation.
|
||||||
|
*/
|
||||||
|
loaderId: Network.LoaderId;
|
||||||
|
navigationType: "reload"|"reloadBypassingCache"|"restore"|"restoreWithPost"|"historySameDocument"|"historyDifferentDocument"|"sameDocument"|"differentDocument";
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Fired when a renderer-initiated navigation is requested.
|
* Fired when a renderer-initiated navigation is requested.
|
||||||
Navigation may still be cancelled after the event is issued.
|
Navigation may still be cancelled after the event is issued.
|
||||||
|
|
@ -14281,7 +14253,7 @@ For cached script it is the last time the cache entry was validated.
|
||||||
/**
|
/**
|
||||||
* Enum of possible storage types.
|
* Enum of possible storage types.
|
||||||
*/
|
*/
|
||||||
export type StorageType = "appcache"|"cookies"|"file_systems"|"indexeddb"|"local_storage"|"shader_cache"|"websql"|"service_workers"|"cache_storage"|"interest_groups"|"shared_storage"|"storage_buckets"|"all"|"other";
|
export type StorageType = "cookies"|"file_systems"|"indexeddb"|"local_storage"|"shader_cache"|"websql"|"service_workers"|"cache_storage"|"interest_groups"|"shared_storage"|"storage_buckets"|"all"|"other";
|
||||||
/**
|
/**
|
||||||
* Usage for a storage type.
|
* Usage for a storage type.
|
||||||
*/
|
*/
|
||||||
|
|
@ -15193,6 +15165,28 @@ session. The effective Related Website Sets will not change during a browser ses
|
||||||
export type getRelatedWebsiteSetsReturnValue = {
|
export type getRelatedWebsiteSetsReturnValue = {
|
||||||
sets: RelatedWebsiteSet[];
|
sets: RelatedWebsiteSet[];
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Returns the list of URLs from a page and its embedded resources that match
|
||||||
|
existing grace period URL pattern rules.
|
||||||
|
https://developers.google.com/privacy-sandbox/cookies/temporary-exceptions/grace-period
|
||||||
|
*/
|
||||||
|
export type getAffectedUrlsForThirdPartyCookieMetadataParameters = {
|
||||||
|
/**
|
||||||
|
* The URL of the page currently being visited.
|
||||||
|
*/
|
||||||
|
firstPartyUrl: string;
|
||||||
|
/**
|
||||||
|
* The list of embedded resource URLs from the page.
|
||||||
|
*/
|
||||||
|
thirdPartyUrls: string[];
|
||||||
|
}
|
||||||
|
export type getAffectedUrlsForThirdPartyCookieMetadataReturnValue = {
|
||||||
|
/**
|
||||||
|
* Array of matching URLs. If there is a primary pattern match for the first-
|
||||||
|
party URL, only the first-party URL is returned in the array.
|
||||||
|
*/
|
||||||
|
matchedUrls: string[];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -15485,6 +15479,10 @@ If filter is not specified, the one assumed is
|
||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The state of the target window.
|
||||||
|
*/
|
||||||
|
export type WindowState = "normal"|"minimized"|"maximized"|"fullscreen";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Issued when attached to target because of auto-attach or `attachToTarget` command.
|
* Issued when attached to target because of auto-attach or `attachToTarget` command.
|
||||||
|
|
@ -15677,37 +15675,42 @@ Parts of the URL other than those constituting origin are ignored.
|
||||||
*/
|
*/
|
||||||
url: string;
|
url: string;
|
||||||
/**
|
/**
|
||||||
* Frame left origin in DIP (headless chrome only).
|
* Frame left origin in DIP (requires newWindow to be true or headless shell).
|
||||||
*/
|
*/
|
||||||
left?: number;
|
left?: number;
|
||||||
/**
|
/**
|
||||||
* Frame top origin in DIP (headless chrome only).
|
* Frame top origin in DIP (requires newWindow to be true or headless shell).
|
||||||
*/
|
*/
|
||||||
top?: number;
|
top?: number;
|
||||||
/**
|
/**
|
||||||
* Frame width in DIP (headless chrome only).
|
* Frame width in DIP (requires newWindow to be true or headless shell).
|
||||||
*/
|
*/
|
||||||
width?: number;
|
width?: number;
|
||||||
/**
|
/**
|
||||||
* Frame height in DIP (headless chrome only).
|
* Frame height in DIP (requires newWindow to be true or headless shell).
|
||||||
*/
|
*/
|
||||||
height?: number;
|
height?: number;
|
||||||
|
/**
|
||||||
|
* Frame window state (requires newWindow to be true or headless shell).
|
||||||
|
Default is normal.
|
||||||
|
*/
|
||||||
|
windowState?: WindowState;
|
||||||
/**
|
/**
|
||||||
* The browser context to create the page in.
|
* The browser context to create the page in.
|
||||||
*/
|
*/
|
||||||
browserContextId?: Browser.BrowserContextID;
|
browserContextId?: Browser.BrowserContextID;
|
||||||
/**
|
/**
|
||||||
* Whether BeginFrames for this target will be controlled via DevTools (headless chrome only,
|
* Whether BeginFrames for this target will be controlled via DevTools (headless shell only,
|
||||||
not supported on MacOS yet, false by default).
|
not supported on MacOS yet, false by default).
|
||||||
*/
|
*/
|
||||||
enableBeginFrameControl?: boolean;
|
enableBeginFrameControl?: boolean;
|
||||||
/**
|
/**
|
||||||
* Whether to create a new Window or Tab (chrome-only, false by default).
|
* Whether to create a new Window or Tab (false by default, not supported by headless shell).
|
||||||
*/
|
*/
|
||||||
newWindow?: boolean;
|
newWindow?: boolean;
|
||||||
/**
|
/**
|
||||||
* Whether to create the target in background or foreground (chrome-only,
|
* Whether to create the target in background or foreground (false by default, not supported
|
||||||
false by default).
|
by headless shell).
|
||||||
*/
|
*/
|
||||||
background?: boolean;
|
background?: boolean;
|
||||||
/**
|
/**
|
||||||
|
|
@ -18012,9 +18015,20 @@ variables as its properties.
|
||||||
*/
|
*/
|
||||||
externalURL?: string;
|
externalURL?: string;
|
||||||
}
|
}
|
||||||
|
export interface ResolvedBreakpoint {
|
||||||
|
/**
|
||||||
|
* Breakpoint unique identifier.
|
||||||
|
*/
|
||||||
|
breakpointId: BreakpointId;
|
||||||
|
/**
|
||||||
|
* Actual breakpoint location.
|
||||||
|
*/
|
||||||
|
location: Location;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired when breakpoint is resolved to an actual script and location.
|
* Fired when breakpoint is resolved to an actual script and location.
|
||||||
|
Deprecated in favor of `resolvedBreakpoints` in the `scriptParsed` event.
|
||||||
*/
|
*/
|
||||||
export type breakpointResolvedPayload = {
|
export type breakpointResolvedPayload = {
|
||||||
/**
|
/**
|
||||||
|
|
@ -18225,6 +18239,12 @@ scripts upon enabling debugger.
|
||||||
* The name the embedder supplied for this script.
|
* The name the embedder supplied for this script.
|
||||||
*/
|
*/
|
||||||
embedderName?: string;
|
embedderName?: string;
|
||||||
|
/**
|
||||||
|
* The list of set breakpoints in this script if calls to `setBreakpointByUrl`
|
||||||
|
matches this script's URL or hash. Clients that use this list can ignore the
|
||||||
|
`breakpointResolved` event. They are equivalent.
|
||||||
|
*/
|
||||||
|
resolvedBreakpoints?: ResolvedBreakpoint[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -20150,13 +20170,21 @@ It is the total usage of the corresponding isolate not scoped to a particular Ru
|
||||||
}
|
}
|
||||||
export type getHeapUsageReturnValue = {
|
export type getHeapUsageReturnValue = {
|
||||||
/**
|
/**
|
||||||
* Used heap size in bytes.
|
* Used JavaScript heap size in bytes.
|
||||||
*/
|
*/
|
||||||
usedSize: number;
|
usedSize: number;
|
||||||
/**
|
/**
|
||||||
* Allocated heap size in bytes.
|
* Allocated JavaScript heap size in bytes.
|
||||||
*/
|
*/
|
||||||
totalSize: number;
|
totalSize: number;
|
||||||
|
/**
|
||||||
|
* Used size in bytes in the embedder's garbage-collected heap.
|
||||||
|
*/
|
||||||
|
embedderHeapUsedSize: number;
|
||||||
|
/**
|
||||||
|
* Size in bytes of backing storage for array buffers and external strings.
|
||||||
|
*/
|
||||||
|
backingStorageSize: number;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Returns properties of a given object. Object group of the result is inherited from the target
|
* Returns properties of a given object. Object group of the result is inherited from the target
|
||||||
|
|
@ -20472,7 +20500,6 @@ Error was thrown.
|
||||||
"DOMStorage.domStorageItemRemoved": DOMStorage.domStorageItemRemovedPayload;
|
"DOMStorage.domStorageItemRemoved": DOMStorage.domStorageItemRemovedPayload;
|
||||||
"DOMStorage.domStorageItemUpdated": DOMStorage.domStorageItemUpdatedPayload;
|
"DOMStorage.domStorageItemUpdated": DOMStorage.domStorageItemUpdatedPayload;
|
||||||
"DOMStorage.domStorageItemsCleared": DOMStorage.domStorageItemsClearedPayload;
|
"DOMStorage.domStorageItemsCleared": DOMStorage.domStorageItemsClearedPayload;
|
||||||
"Database.addDatabase": Database.addDatabasePayload;
|
|
||||||
"Emulation.virtualTimeBudgetExpired": Emulation.virtualTimeBudgetExpiredPayload;
|
"Emulation.virtualTimeBudgetExpired": Emulation.virtualTimeBudgetExpiredPayload;
|
||||||
"Input.dragIntercepted": Input.dragInterceptedPayload;
|
"Input.dragIntercepted": Input.dragInterceptedPayload;
|
||||||
"Inspector.detached": Inspector.detachedPayload;
|
"Inspector.detached": Inspector.detachedPayload;
|
||||||
|
|
@ -20526,6 +20553,7 @@ Error was thrown.
|
||||||
"Page.frameNavigated": Page.frameNavigatedPayload;
|
"Page.frameNavigated": Page.frameNavigatedPayload;
|
||||||
"Page.documentOpened": Page.documentOpenedPayload;
|
"Page.documentOpened": Page.documentOpenedPayload;
|
||||||
"Page.frameResized": Page.frameResizedPayload;
|
"Page.frameResized": Page.frameResizedPayload;
|
||||||
|
"Page.frameStartedNavigating": Page.frameStartedNavigatingPayload;
|
||||||
"Page.frameRequestedNavigation": Page.frameRequestedNavigationPayload;
|
"Page.frameRequestedNavigation": Page.frameRequestedNavigationPayload;
|
||||||
"Page.frameScheduledNavigation": Page.frameScheduledNavigationPayload;
|
"Page.frameScheduledNavigation": Page.frameScheduledNavigationPayload;
|
||||||
"Page.frameStartedLoading": Page.frameStartedLoadingPayload;
|
"Page.frameStartedLoading": Page.frameStartedLoadingPayload;
|
||||||
|
|
@ -20656,6 +20684,7 @@ Error was thrown.
|
||||||
"Audits.checkContrast": Audits.checkContrastParameters;
|
"Audits.checkContrast": Audits.checkContrastParameters;
|
||||||
"Audits.checkFormsIssues": Audits.checkFormsIssuesParameters;
|
"Audits.checkFormsIssues": Audits.checkFormsIssuesParameters;
|
||||||
"Extensions.loadUnpacked": Extensions.loadUnpackedParameters;
|
"Extensions.loadUnpacked": Extensions.loadUnpackedParameters;
|
||||||
|
"Extensions.uninstall": Extensions.uninstallParameters;
|
||||||
"Extensions.getStorageItems": Extensions.getStorageItemsParameters;
|
"Extensions.getStorageItems": Extensions.getStorageItemsParameters;
|
||||||
"Extensions.removeStorageItems": Extensions.removeStorageItemsParameters;
|
"Extensions.removeStorageItems": Extensions.removeStorageItemsParameters;
|
||||||
"Extensions.clearStorageItems": Extensions.clearStorageItemsParameters;
|
"Extensions.clearStorageItems": Extensions.clearStorageItemsParameters;
|
||||||
|
|
@ -20808,10 +20837,6 @@ Error was thrown.
|
||||||
"DOMStorage.getDOMStorageItems": DOMStorage.getDOMStorageItemsParameters;
|
"DOMStorage.getDOMStorageItems": DOMStorage.getDOMStorageItemsParameters;
|
||||||
"DOMStorage.removeDOMStorageItem": DOMStorage.removeDOMStorageItemParameters;
|
"DOMStorage.removeDOMStorageItem": DOMStorage.removeDOMStorageItemParameters;
|
||||||
"DOMStorage.setDOMStorageItem": DOMStorage.setDOMStorageItemParameters;
|
"DOMStorage.setDOMStorageItem": DOMStorage.setDOMStorageItemParameters;
|
||||||
"Database.disable": Database.disableParameters;
|
|
||||||
"Database.enable": Database.enableParameters;
|
|
||||||
"Database.executeSQL": Database.executeSQLParameters;
|
|
||||||
"Database.getDatabaseTableNames": Database.getDatabaseTableNamesParameters;
|
|
||||||
"DeviceOrientation.clearDeviceOrientationOverride": DeviceOrientation.clearDeviceOrientationOverrideParameters;
|
"DeviceOrientation.clearDeviceOrientationOverride": DeviceOrientation.clearDeviceOrientationOverrideParameters;
|
||||||
"DeviceOrientation.setDeviceOrientationOverride": DeviceOrientation.setDeviceOrientationOverrideParameters;
|
"DeviceOrientation.setDeviceOrientationOverride": DeviceOrientation.setDeviceOrientationOverrideParameters;
|
||||||
"Emulation.canEmulate": Emulation.canEmulateParameters;
|
"Emulation.canEmulate": Emulation.canEmulateParameters;
|
||||||
|
|
@ -21088,6 +21113,7 @@ Error was thrown.
|
||||||
"Storage.setAttributionReportingTracking": Storage.setAttributionReportingTrackingParameters;
|
"Storage.setAttributionReportingTracking": Storage.setAttributionReportingTrackingParameters;
|
||||||
"Storage.sendPendingAttributionReports": Storage.sendPendingAttributionReportsParameters;
|
"Storage.sendPendingAttributionReports": Storage.sendPendingAttributionReportsParameters;
|
||||||
"Storage.getRelatedWebsiteSets": Storage.getRelatedWebsiteSetsParameters;
|
"Storage.getRelatedWebsiteSets": Storage.getRelatedWebsiteSetsParameters;
|
||||||
|
"Storage.getAffectedUrlsForThirdPartyCookieMetadata": Storage.getAffectedUrlsForThirdPartyCookieMetadataParameters;
|
||||||
"SystemInfo.getInfo": SystemInfo.getInfoParameters;
|
"SystemInfo.getInfo": SystemInfo.getInfoParameters;
|
||||||
"SystemInfo.getFeatureState": SystemInfo.getFeatureStateParameters;
|
"SystemInfo.getFeatureState": SystemInfo.getFeatureStateParameters;
|
||||||
"SystemInfo.getProcessInfo": SystemInfo.getProcessInfoParameters;
|
"SystemInfo.getProcessInfo": SystemInfo.getProcessInfoParameters;
|
||||||
|
|
@ -21273,6 +21299,7 @@ Error was thrown.
|
||||||
"Audits.checkContrast": Audits.checkContrastReturnValue;
|
"Audits.checkContrast": Audits.checkContrastReturnValue;
|
||||||
"Audits.checkFormsIssues": Audits.checkFormsIssuesReturnValue;
|
"Audits.checkFormsIssues": Audits.checkFormsIssuesReturnValue;
|
||||||
"Extensions.loadUnpacked": Extensions.loadUnpackedReturnValue;
|
"Extensions.loadUnpacked": Extensions.loadUnpackedReturnValue;
|
||||||
|
"Extensions.uninstall": Extensions.uninstallReturnValue;
|
||||||
"Extensions.getStorageItems": Extensions.getStorageItemsReturnValue;
|
"Extensions.getStorageItems": Extensions.getStorageItemsReturnValue;
|
||||||
"Extensions.removeStorageItems": Extensions.removeStorageItemsReturnValue;
|
"Extensions.removeStorageItems": Extensions.removeStorageItemsReturnValue;
|
||||||
"Extensions.clearStorageItems": Extensions.clearStorageItemsReturnValue;
|
"Extensions.clearStorageItems": Extensions.clearStorageItemsReturnValue;
|
||||||
|
|
@ -21425,10 +21452,6 @@ Error was thrown.
|
||||||
"DOMStorage.getDOMStorageItems": DOMStorage.getDOMStorageItemsReturnValue;
|
"DOMStorage.getDOMStorageItems": DOMStorage.getDOMStorageItemsReturnValue;
|
||||||
"DOMStorage.removeDOMStorageItem": DOMStorage.removeDOMStorageItemReturnValue;
|
"DOMStorage.removeDOMStorageItem": DOMStorage.removeDOMStorageItemReturnValue;
|
||||||
"DOMStorage.setDOMStorageItem": DOMStorage.setDOMStorageItemReturnValue;
|
"DOMStorage.setDOMStorageItem": DOMStorage.setDOMStorageItemReturnValue;
|
||||||
"Database.disable": Database.disableReturnValue;
|
|
||||||
"Database.enable": Database.enableReturnValue;
|
|
||||||
"Database.executeSQL": Database.executeSQLReturnValue;
|
|
||||||
"Database.getDatabaseTableNames": Database.getDatabaseTableNamesReturnValue;
|
|
||||||
"DeviceOrientation.clearDeviceOrientationOverride": DeviceOrientation.clearDeviceOrientationOverrideReturnValue;
|
"DeviceOrientation.clearDeviceOrientationOverride": DeviceOrientation.clearDeviceOrientationOverrideReturnValue;
|
||||||
"DeviceOrientation.setDeviceOrientationOverride": DeviceOrientation.setDeviceOrientationOverrideReturnValue;
|
"DeviceOrientation.setDeviceOrientationOverride": DeviceOrientation.setDeviceOrientationOverrideReturnValue;
|
||||||
"Emulation.canEmulate": Emulation.canEmulateReturnValue;
|
"Emulation.canEmulate": Emulation.canEmulateReturnValue;
|
||||||
|
|
@ -21705,6 +21728,7 @@ Error was thrown.
|
||||||
"Storage.setAttributionReportingTracking": Storage.setAttributionReportingTrackingReturnValue;
|
"Storage.setAttributionReportingTracking": Storage.setAttributionReportingTrackingReturnValue;
|
||||||
"Storage.sendPendingAttributionReports": Storage.sendPendingAttributionReportsReturnValue;
|
"Storage.sendPendingAttributionReports": Storage.sendPendingAttributionReportsReturnValue;
|
||||||
"Storage.getRelatedWebsiteSets": Storage.getRelatedWebsiteSetsReturnValue;
|
"Storage.getRelatedWebsiteSets": Storage.getRelatedWebsiteSetsReturnValue;
|
||||||
|
"Storage.getAffectedUrlsForThirdPartyCookieMetadata": Storage.getAffectedUrlsForThirdPartyCookieMetadataReturnValue;
|
||||||
"SystemInfo.getInfo": SystemInfo.getInfoReturnValue;
|
"SystemInfo.getInfo": SystemInfo.getInfoReturnValue;
|
||||||
"SystemInfo.getFeatureState": SystemInfo.getFeatureStateReturnValue;
|
"SystemInfo.getFeatureState": SystemInfo.getFeatureStateReturnValue;
|
||||||
"SystemInfo.getProcessInfo": SystemInfo.getProcessInfoReturnValue;
|
"SystemInfo.getProcessInfo": SystemInfo.getProcessInfoReturnValue;
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert, monotonicTime } from '../../utils';
|
import { assert, monotonicTime } from '../../utils';
|
||||||
import { launchProcess } from '../../utils/processLauncher';
|
|
||||||
import { serverSideCallMetadata } from '../instrumentation';
|
import { serverSideCallMetadata } from '../instrumentation';
|
||||||
import { Page } from '../page';
|
import { Page } from '../page';
|
||||||
|
import { launchProcess } from '../processLauncher';
|
||||||
import { ProgressController } from '../progress';
|
import { ProgressController } from '../progress';
|
||||||
|
|
||||||
import type { Progress } from '../progress';
|
import type { Progress } from '../progress';
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SdkObject, createInstrumentation, serverSideCallMetadata } from './instrumentation';
|
import { SdkObject, createInstrumentation, serverSideCallMetadata } from './instrumentation';
|
||||||
|
import { gracefullyProcessExitDoNotHang } from './processLauncher';
|
||||||
import { Recorder } from './recorder';
|
import { Recorder } from './recorder';
|
||||||
import { asLocator } from '../utils';
|
import { asLocator } from '../utils';
|
||||||
import { parseAriaSnapshotUnsafe } from '../utils/isomorphic/ariaSnapshot';
|
import { parseAriaSnapshotUnsafe } from '../utils/isomorphic/ariaSnapshot';
|
||||||
import { yaml } from '../utilsBundle';
|
import { yaml } from '../utilsBundle';
|
||||||
import { EmptyRecorderApp } from './recorder/recorderApp';
|
import { EmptyRecorderApp } from './recorder/recorderApp';
|
||||||
import { unsafeLocatorOrSelectorAsSelector } from '../utils/isomorphic/locatorParser';
|
import { unsafeLocatorOrSelectorAsSelector } from '../utils/isomorphic/locatorParser';
|
||||||
import { gracefullyProcessExitDoNotHang } from '../utils/processLauncher';
|
|
||||||
|
|
||||||
import type { Language } from '../utils';
|
import type { Language } from '../utils';
|
||||||
import type { Browser } from './browser';
|
import type { Browser } from './browser';
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@
|
||||||
"defaultBrowserType": "webkit"
|
"defaultBrowserType": "webkit"
|
||||||
},
|
},
|
||||||
"Galaxy S5": {
|
"Galaxy S5": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 360,
|
"width": 360,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -121,7 +121,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy S5 landscape": {
|
"Galaxy S5 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 360
|
"height": 360
|
||||||
|
|
@ -132,7 +132,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy S8": {
|
"Galaxy S8": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 360,
|
"width": 360,
|
||||||
"height": 740
|
"height": 740
|
||||||
|
|
@ -143,7 +143,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy S8 landscape": {
|
"Galaxy S8 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 740,
|
"width": 740,
|
||||||
"height": 360
|
"height": 360
|
||||||
|
|
@ -154,7 +154,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy S9+": {
|
"Galaxy S9+": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 320,
|
"width": 320,
|
||||||
"height": 658
|
"height": 658
|
||||||
|
|
@ -165,7 +165,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy S9+ landscape": {
|
"Galaxy S9+ landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 658,
|
"width": 658,
|
||||||
"height": 320
|
"height": 320
|
||||||
|
|
@ -176,7 +176,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy Tab S4": {
|
"Galaxy Tab S4": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 712,
|
"width": 712,
|
||||||
"height": 1138
|
"height": 1138
|
||||||
|
|
@ -187,7 +187,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Galaxy Tab S4 landscape": {
|
"Galaxy Tab S4 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 1138,
|
"width": 1138,
|
||||||
"height": 712
|
"height": 712
|
||||||
|
|
@ -1098,7 +1098,7 @@
|
||||||
"defaultBrowserType": "webkit"
|
"defaultBrowserType": "webkit"
|
||||||
},
|
},
|
||||||
"LG Optimus L70": {
|
"LG Optimus L70": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 384,
|
"width": 384,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -1109,7 +1109,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"LG Optimus L70 landscape": {
|
"LG Optimus L70 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 384
|
"height": 384
|
||||||
|
|
@ -1120,7 +1120,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Microsoft Lumia 550": {
|
"Microsoft Lumia 550": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36 Edge/14.14263",
|
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36 Edge/14.14263",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 360,
|
"width": 360,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -1131,7 +1131,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Microsoft Lumia 550 landscape": {
|
"Microsoft Lumia 550 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36 Edge/14.14263",
|
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36 Edge/14.14263",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 360
|
"height": 360
|
||||||
|
|
@ -1142,7 +1142,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Microsoft Lumia 950": {
|
"Microsoft Lumia 950": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36 Edge/14.14263",
|
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36 Edge/14.14263",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 360,
|
"width": 360,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -1153,7 +1153,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Microsoft Lumia 950 landscape": {
|
"Microsoft Lumia 950 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36 Edge/14.14263",
|
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36 Edge/14.14263",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 360
|
"height": 360
|
||||||
|
|
@ -1164,7 +1164,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 10": {
|
"Nexus 10": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 800,
|
"width": 800,
|
||||||
"height": 1280
|
"height": 1280
|
||||||
|
|
@ -1175,7 +1175,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 10 landscape": {
|
"Nexus 10 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 1280,
|
"width": 1280,
|
||||||
"height": 800
|
"height": 800
|
||||||
|
|
@ -1186,7 +1186,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 4": {
|
"Nexus 4": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 384,
|
"width": 384,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -1197,7 +1197,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 4 landscape": {
|
"Nexus 4 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 384
|
"height": 384
|
||||||
|
|
@ -1208,7 +1208,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 5": {
|
"Nexus 5": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 360,
|
"width": 360,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -1219,7 +1219,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 5 landscape": {
|
"Nexus 5 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 360
|
"height": 360
|
||||||
|
|
@ -1230,7 +1230,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 5X": {
|
"Nexus 5X": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 412,
|
"width": 412,
|
||||||
"height": 732
|
"height": 732
|
||||||
|
|
@ -1241,7 +1241,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 5X landscape": {
|
"Nexus 5X landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 732,
|
"width": 732,
|
||||||
"height": 412
|
"height": 412
|
||||||
|
|
@ -1252,7 +1252,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 6": {
|
"Nexus 6": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 412,
|
"width": 412,
|
||||||
"height": 732
|
"height": 732
|
||||||
|
|
@ -1263,7 +1263,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 6 landscape": {
|
"Nexus 6 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 732,
|
"width": 732,
|
||||||
"height": 412
|
"height": 412
|
||||||
|
|
@ -1274,7 +1274,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 6P": {
|
"Nexus 6P": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 412,
|
"width": 412,
|
||||||
"height": 732
|
"height": 732
|
||||||
|
|
@ -1285,7 +1285,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 6P landscape": {
|
"Nexus 6P landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 732,
|
"width": 732,
|
||||||
"height": 412
|
"height": 412
|
||||||
|
|
@ -1296,7 +1296,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 7": {
|
"Nexus 7": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 600,
|
"width": 600,
|
||||||
"height": 960
|
"height": 960
|
||||||
|
|
@ -1307,7 +1307,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Nexus 7 landscape": {
|
"Nexus 7 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 960,
|
"width": 960,
|
||||||
"height": 600
|
"height": 600
|
||||||
|
|
@ -1362,7 +1362,7 @@
|
||||||
"defaultBrowserType": "webkit"
|
"defaultBrowserType": "webkit"
|
||||||
},
|
},
|
||||||
"Pixel 2": {
|
"Pixel 2": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 411,
|
"width": 411,
|
||||||
"height": 731
|
"height": 731
|
||||||
|
|
@ -1373,7 +1373,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 2 landscape": {
|
"Pixel 2 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 731,
|
"width": 731,
|
||||||
"height": 411
|
"height": 411
|
||||||
|
|
@ -1384,7 +1384,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 2 XL": {
|
"Pixel 2 XL": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 411,
|
"width": 411,
|
||||||
"height": 823
|
"height": 823
|
||||||
|
|
@ -1395,7 +1395,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 2 XL landscape": {
|
"Pixel 2 XL landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 823,
|
"width": 823,
|
||||||
"height": 411
|
"height": 411
|
||||||
|
|
@ -1406,7 +1406,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 3": {
|
"Pixel 3": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 393,
|
"width": 393,
|
||||||
"height": 786
|
"height": 786
|
||||||
|
|
@ -1417,7 +1417,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 3 landscape": {
|
"Pixel 3 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 786,
|
"width": 786,
|
||||||
"height": 393
|
"height": 393
|
||||||
|
|
@ -1428,7 +1428,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 4": {
|
"Pixel 4": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 353,
|
"width": 353,
|
||||||
"height": 745
|
"height": 745
|
||||||
|
|
@ -1439,7 +1439,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 4 landscape": {
|
"Pixel 4 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 745,
|
"width": 745,
|
||||||
"height": 353
|
"height": 353
|
||||||
|
|
@ -1450,7 +1450,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 4a (5G)": {
|
"Pixel 4a (5G)": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 412,
|
"width": 412,
|
||||||
"height": 892
|
"height": 892
|
||||||
|
|
@ -1465,7 +1465,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 4a (5G) landscape": {
|
"Pixel 4a (5G) landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"height": 892,
|
"height": 892,
|
||||||
"width": 412
|
"width": 412
|
||||||
|
|
@ -1480,7 +1480,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 5": {
|
"Pixel 5": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 393,
|
"width": 393,
|
||||||
"height": 851
|
"height": 851
|
||||||
|
|
@ -1495,7 +1495,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 5 landscape": {
|
"Pixel 5 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 851,
|
"width": 851,
|
||||||
"height": 393
|
"height": 393
|
||||||
|
|
@ -1510,7 +1510,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 7": {
|
"Pixel 7": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 412,
|
"width": 412,
|
||||||
"height": 915
|
"height": 915
|
||||||
|
|
@ -1525,7 +1525,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Pixel 7 landscape": {
|
"Pixel 7 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 915,
|
"width": 915,
|
||||||
"height": 412
|
"height": 412
|
||||||
|
|
@ -1540,7 +1540,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Moto G4": {
|
"Moto G4": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 360,
|
"width": 360,
|
||||||
"height": 640
|
"height": 640
|
||||||
|
|
@ -1551,7 +1551,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Moto G4 landscape": {
|
"Moto G4 landscape": {
|
||||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Mobile Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Mobile Safari/537.36",
|
||||||
"viewport": {
|
"viewport": {
|
||||||
"width": 640,
|
"width": 640,
|
||||||
"height": 360
|
"height": 360
|
||||||
|
|
@ -1562,7 +1562,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Desktop Chrome HiDPI": {
|
"Desktop Chrome HiDPI": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 1792,
|
"width": 1792,
|
||||||
"height": 1120
|
"height": 1120
|
||||||
|
|
@ -1577,7 +1577,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Desktop Edge HiDPI": {
|
"Desktop Edge HiDPI": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Safari/537.36 Edg/133.0.6943.35",
|
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36 Edg/134.0.6998.3",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 1792,
|
"width": 1792,
|
||||||
"height": 1120
|
"height": 1120
|
||||||
|
|
@ -1592,7 +1592,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Desktop Firefox HiDPI": {
|
"Desktop Firefox HiDPI": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
|
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 1792,
|
"width": 1792,
|
||||||
"height": 1120
|
"height": 1120
|
||||||
|
|
@ -1622,7 +1622,7 @@
|
||||||
"defaultBrowserType": "webkit"
|
"defaultBrowserType": "webkit"
|
||||||
},
|
},
|
||||||
"Desktop Chrome": {
|
"Desktop Chrome": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Safari/537.36",
|
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 1920,
|
"width": 1920,
|
||||||
"height": 1080
|
"height": 1080
|
||||||
|
|
@ -1637,7 +1637,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Desktop Edge": {
|
"Desktop Edge": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.35 Safari/537.36 Edg/133.0.6943.35",
|
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.3 Safari/537.36 Edg/134.0.6998.3",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 1920,
|
"width": 1920,
|
||||||
"height": 1080
|
"height": 1080
|
||||||
|
|
@ -1652,7 +1652,7 @@
|
||||||
"defaultBrowserType": "chromium"
|
"defaultBrowserType": "chromium"
|
||||||
},
|
},
|
||||||
"Desktop Firefox": {
|
"Desktop Firefox": {
|
||||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
|
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0",
|
||||||
"screen": {
|
"screen": {
|
||||||
"width": 1920,
|
"width": 1920,
|
||||||
"height": 1080
|
"height": 1080
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import * as fs from 'fs';
|
||||||
|
|
||||||
import { Dispatcher, existingDispatcher } from './dispatcher';
|
import { Dispatcher, existingDispatcher } from './dispatcher';
|
||||||
import { StreamDispatcher } from './streamDispatcher';
|
import { StreamDispatcher } from './streamDispatcher';
|
||||||
import { mkdirIfNeeded } from '../../utils/fileUtils';
|
import { mkdirIfNeeded } from '../fileUtils';
|
||||||
|
|
||||||
import type { DispatcherScope } from './dispatcher';
|
import type { DispatcherScope } from './dispatcher';
|
||||||
import type { Artifact } from '../artifact';
|
import type { Artifact } from '../artifact';
|
||||||
|
|
|
||||||
|
|
@ -14,45 +14,27 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as os from 'os';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
import { Dispatcher } from './dispatcher';
|
import { Dispatcher } from './dispatcher';
|
||||||
import { SdkObject } from '../../server/instrumentation';
|
import { SdkObject } from '../../server/instrumentation';
|
||||||
import { assert, calculateSha1, createGuid, removeFolders } from '../../utils';
|
import * as localUtils from '../../utils/localUtils';
|
||||||
import { serializeClientSideCallMetadata } from '../../utils';
|
import { nodePlatform } from '../../utils/platform';
|
||||||
import { ManualPromise } from '../../utils/manualPromise';
|
|
||||||
import { fetchData } from '../../utils/network';
|
|
||||||
import { getUserAgent } from '../../utils/userAgent';
|
import { getUserAgent } from '../../utils/userAgent';
|
||||||
import { ZipFile } from '../../utils/zipFile';
|
|
||||||
import { yauzl, yazl } from '../../zipBundle';
|
|
||||||
import { deviceDescriptors as descriptors } from '../deviceDescriptors';
|
import { deviceDescriptors as descriptors } from '../deviceDescriptors';
|
||||||
import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher';
|
import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher';
|
||||||
import { ProgressController } from '../progress';
|
import { ProgressController } from '../progress';
|
||||||
import { SocksInterceptor } from '../socksInterceptor';
|
import { SocksInterceptor } from '../socksInterceptor';
|
||||||
import { WebSocketTransport } from '../transport';
|
import { WebSocketTransport } from '../transport';
|
||||||
|
|
||||||
import type { HTTPRequestParams } from '../../utils/network';
|
import type { HarBackend } from '../../utils/harBackend';
|
||||||
import type { CallMetadata } from '../instrumentation';
|
import type { CallMetadata } from '../instrumentation';
|
||||||
import type { Playwright } from '../playwright';
|
import type { Playwright } from '../playwright';
|
||||||
import type { Progress } from '../progress';
|
|
||||||
import type { HeadersArray } from '../types';
|
|
||||||
import type { RootDispatcher } from './dispatcher';
|
import type { RootDispatcher } from './dispatcher';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import type * as har from '@trace/har';
|
|
||||||
import type EventEmitter from 'events';
|
|
||||||
import type http from 'http';
|
|
||||||
|
|
||||||
export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.LocalUtilsChannel, RootDispatcher> implements channels.LocalUtilsChannel {
|
export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.LocalUtilsChannel, RootDispatcher> implements channels.LocalUtilsChannel {
|
||||||
_type_LocalUtils: boolean;
|
_type_LocalUtils: boolean;
|
||||||
private _harBackends = new Map<string, HarBackend>();
|
private _harBackends = new Map<string, HarBackend>();
|
||||||
private _stackSessions = new Map<string, {
|
private _stackSessions = new Map<string, localUtils.StackSession>();
|
||||||
file: string,
|
|
||||||
writer: Promise<void>,
|
|
||||||
tmpDir: string | undefined,
|
|
||||||
callStacks: channels.ClientSideCallMetadata[]
|
|
||||||
}>();
|
|
||||||
|
|
||||||
constructor(scope: RootDispatcher, playwright: Playwright) {
|
constructor(scope: RootDispatcher, playwright: Playwright) {
|
||||||
const localUtils = new SdkObject(playwright, 'localUtils', 'localUtils');
|
const localUtils = new SdkObject(playwright, 'localUtils', 'localUtils');
|
||||||
|
|
@ -65,139 +47,35 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
|
||||||
}
|
}
|
||||||
|
|
||||||
async zip(params: channels.LocalUtilsZipParams): Promise<void> {
|
async zip(params: channels.LocalUtilsZipParams): Promise<void> {
|
||||||
const promise = new ManualPromise<void>();
|
return await localUtils.zip(nodePlatform, this._stackSessions, params);
|
||||||
const zipFile = new yazl.ZipFile();
|
|
||||||
(zipFile as any as EventEmitter).on('error', error => promise.reject(error));
|
|
||||||
|
|
||||||
const addFile = (file: string, name: string) => {
|
|
||||||
try {
|
|
||||||
if (fs.statSync(file).isFile())
|
|
||||||
zipFile.addFile(file, name);
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const entry of params.entries)
|
|
||||||
addFile(entry.value, entry.name);
|
|
||||||
|
|
||||||
// Add stacks and the sources.
|
|
||||||
const stackSession = params.stacksId ? this._stackSessions.get(params.stacksId) : undefined;
|
|
||||||
if (stackSession?.callStacks.length) {
|
|
||||||
await stackSession.writer;
|
|
||||||
if (process.env.PW_LIVE_TRACE_STACKS) {
|
|
||||||
zipFile.addFile(stackSession.file, 'trace.stacks');
|
|
||||||
} else {
|
|
||||||
const buffer = Buffer.from(JSON.stringify(serializeClientSideCallMetadata(stackSession.callStacks)));
|
|
||||||
zipFile.addBuffer(buffer, 'trace.stacks');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect sources from stacks.
|
|
||||||
if (params.includeSources) {
|
|
||||||
const sourceFiles = new Set<string>();
|
|
||||||
for (const { stack } of stackSession?.callStacks || []) {
|
|
||||||
if (!stack)
|
|
||||||
continue;
|
|
||||||
for (const { file } of stack)
|
|
||||||
sourceFiles.add(file);
|
|
||||||
}
|
|
||||||
for (const sourceFile of sourceFiles)
|
|
||||||
addFile(sourceFile, 'resources/src@' + calculateSha1(sourceFile) + '.txt');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.mode === 'write') {
|
|
||||||
// New file, just compress the entries.
|
|
||||||
await fs.promises.mkdir(path.dirname(params.zipFile), { recursive: true });
|
|
||||||
zipFile.end(undefined, () => {
|
|
||||||
zipFile.outputStream.pipe(fs.createWriteStream(params.zipFile))
|
|
||||||
.on('close', () => promise.resolve())
|
|
||||||
.on('error', error => promise.reject(error));
|
|
||||||
});
|
|
||||||
await promise;
|
|
||||||
await this._deleteStackSession(params.stacksId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// File already exists. Repack and add new entries.
|
|
||||||
const tempFile = params.zipFile + '.tmp';
|
|
||||||
await fs.promises.rename(params.zipFile, tempFile);
|
|
||||||
|
|
||||||
yauzl.open(tempFile, (err, inZipFile) => {
|
|
||||||
if (err) {
|
|
||||||
promise.reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
assert(inZipFile);
|
|
||||||
let pendingEntries = inZipFile.entryCount;
|
|
||||||
inZipFile.on('entry', entry => {
|
|
||||||
inZipFile.openReadStream(entry, (err, readStream) => {
|
|
||||||
if (err) {
|
|
||||||
promise.reject(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
zipFile.addReadStream(readStream!, entry.fileName);
|
|
||||||
if (--pendingEntries === 0) {
|
|
||||||
zipFile.end(undefined, () => {
|
|
||||||
zipFile.outputStream.pipe(fs.createWriteStream(params.zipFile)).on('close', () => {
|
|
||||||
fs.promises.unlink(tempFile).then(() => {
|
|
||||||
promise.resolve();
|
|
||||||
}).catch(error => promise.reject(error));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await promise;
|
|
||||||
await this._deleteStackSession(params.stacksId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async harOpen(params: channels.LocalUtilsHarOpenParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarOpenResult> {
|
async harOpen(params: channels.LocalUtilsHarOpenParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarOpenResult> {
|
||||||
let harBackend: HarBackend;
|
return await localUtils.harOpen(this._harBackends, params);
|
||||||
if (params.file.endsWith('.zip')) {
|
|
||||||
const zipFile = new ZipFile(params.file);
|
|
||||||
const entryNames = await zipFile.entries();
|
|
||||||
const harEntryName = entryNames.find(e => e.endsWith('.har'));
|
|
||||||
if (!harEntryName)
|
|
||||||
return { error: 'Specified archive does not have a .har file' };
|
|
||||||
const har = await zipFile.read(harEntryName);
|
|
||||||
const harFile = JSON.parse(har.toString()) as har.HARFile;
|
|
||||||
harBackend = new HarBackend(harFile, null, zipFile);
|
|
||||||
} else {
|
|
||||||
const harFile = JSON.parse(await fs.promises.readFile(params.file, 'utf-8')) as har.HARFile;
|
|
||||||
harBackend = new HarBackend(harFile, path.dirname(params.file), null);
|
|
||||||
}
|
|
||||||
this._harBackends.set(harBackend.id, harBackend);
|
|
||||||
return { harId: harBackend.id };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async harLookup(params: channels.LocalUtilsHarLookupParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarLookupResult> {
|
async harLookup(params: channels.LocalUtilsHarLookupParams, metadata: CallMetadata): Promise<channels.LocalUtilsHarLookupResult> {
|
||||||
const harBackend = this._harBackends.get(params.harId);
|
return await localUtils.harLookup(this._harBackends, params);
|
||||||
if (!harBackend)
|
|
||||||
return { action: 'error', message: `Internal error: har was not opened` };
|
|
||||||
return await harBackend.lookup(params.url, params.method, params.headers, params.postData, params.isNavigationRequest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async harClose(params: channels.LocalUtilsHarCloseParams, metadata: CallMetadata): Promise<void> {
|
async harClose(params: channels.LocalUtilsHarCloseParams, metadata: CallMetadata): Promise<void> {
|
||||||
const harBackend = this._harBackends.get(params.harId);
|
return await localUtils.harClose(this._harBackends, params);
|
||||||
if (harBackend) {
|
|
||||||
this._harBackends.delete(harBackend.id);
|
|
||||||
harBackend.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async harUnzip(params: channels.LocalUtilsHarUnzipParams, metadata: CallMetadata): Promise<void> {
|
async harUnzip(params: channels.LocalUtilsHarUnzipParams, metadata: CallMetadata): Promise<void> {
|
||||||
const dir = path.dirname(params.zipFile);
|
return await localUtils.harUnzip(params);
|
||||||
const zipFile = new ZipFile(params.zipFile);
|
}
|
||||||
for (const entry of await zipFile.entries()) {
|
|
||||||
const buffer = await zipFile.read(entry);
|
async tracingStarted(params: channels.LocalUtilsTracingStartedParams, metadata?: CallMetadata | undefined): Promise<channels.LocalUtilsTracingStartedResult> {
|
||||||
if (entry === 'har.har')
|
return await localUtils.tracingStarted(this._stackSessions, params);
|
||||||
await fs.promises.writeFile(params.harFile, buffer);
|
}
|
||||||
else
|
|
||||||
await fs.promises.writeFile(path.join(dir, entry), buffer);
|
async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams, metadata?: CallMetadata | undefined): Promise<void> {
|
||||||
}
|
return await localUtils.traceDiscarded(nodePlatform, this._stackSessions, params);
|
||||||
zipFile.close();
|
}
|
||||||
await fs.promises.unlink(params.zipFile);
|
|
||||||
|
async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams, metadata?: CallMetadata | undefined): Promise<void> {
|
||||||
|
return await localUtils.addStackToTracingNoReply(this._stackSessions, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(params: channels.LocalUtilsConnectParams, metadata: CallMetadata): Promise<channels.LocalUtilsConnectResult> {
|
async connect(params: channels.LocalUtilsConnectParams, metadata: CallMetadata): Promise<channels.LocalUtilsConnectResult> {
|
||||||
|
|
@ -209,7 +87,7 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
|
||||||
'x-playwright-proxy': params.exposeNetwork ?? '',
|
'x-playwright-proxy': params.exposeNetwork ?? '',
|
||||||
...params.headers,
|
...params.headers,
|
||||||
};
|
};
|
||||||
const wsEndpoint = await urlToWSEndpoint(progress, params.wsEndpoint);
|
const wsEndpoint = await localUtils.urlToWSEndpoint(progress, params.wsEndpoint);
|
||||||
|
|
||||||
const transport = await WebSocketTransport.connect(progress, wsEndpoint, wsHeaders, true, 'x-playwright-debug-log');
|
const transport = await WebSocketTransport.connect(progress, wsEndpoint, wsHeaders, true, 'x-playwright-debug-log');
|
||||||
const socksInterceptor = new SocksInterceptor(transport, params.exposeNetwork, params.socksProxyRedirectPortForTest);
|
const socksInterceptor = new SocksInterceptor(transport, params.exposeNetwork, params.socksProxyRedirectPortForTest);
|
||||||
|
|
@ -240,221 +118,4 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
|
||||||
return { pipe, headers: transport.headers };
|
return { pipe, headers: transport.headers };
|
||||||
}, params.timeout || 0);
|
}, params.timeout || 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async tracingStarted(params: channels.LocalUtilsTracingStartedParams, metadata?: CallMetadata | undefined): Promise<channels.LocalUtilsTracingStartedResult> {
|
|
||||||
let tmpDir = undefined;
|
|
||||||
if (!params.tracesDir)
|
|
||||||
tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-tracing-'));
|
|
||||||
const traceStacksFile = path.join(params.tracesDir || tmpDir!, params.traceName + '.stacks');
|
|
||||||
this._stackSessions.set(traceStacksFile, { callStacks: [], file: traceStacksFile, writer: Promise.resolve(), tmpDir });
|
|
||||||
return { stacksId: traceStacksFile };
|
|
||||||
}
|
|
||||||
|
|
||||||
async traceDiscarded(params: channels.LocalUtilsTraceDiscardedParams, metadata?: CallMetadata | undefined): Promise<void> {
|
|
||||||
await this._deleteStackSession(params.stacksId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async addStackToTracingNoReply(params: channels.LocalUtilsAddStackToTracingNoReplyParams, metadata?: CallMetadata | undefined): Promise<void> {
|
|
||||||
for (const session of this._stackSessions.values()) {
|
|
||||||
session.callStacks.push(params.callData);
|
|
||||||
if (process.env.PW_LIVE_TRACE_STACKS) {
|
|
||||||
session.writer = session.writer.then(() => {
|
|
||||||
const buffer = Buffer.from(JSON.stringify(serializeClientSideCallMetadata(session.callStacks)));
|
|
||||||
return fs.promises.writeFile(session.file, buffer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _deleteStackSession(stacksId?: string) {
|
|
||||||
const session = stacksId ? this._stackSessions.get(stacksId) : undefined;
|
|
||||||
if (!session)
|
|
||||||
return;
|
|
||||||
await session.writer;
|
|
||||||
if (session.tmpDir)
|
|
||||||
await removeFolders([session.tmpDir]);
|
|
||||||
this._stackSessions.delete(stacksId!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const redirectStatus = [301, 302, 303, 307, 308];
|
|
||||||
|
|
||||||
class HarBackend {
|
|
||||||
readonly id = createGuid();
|
|
||||||
private _harFile: har.HARFile;
|
|
||||||
private _zipFile: ZipFile | null;
|
|
||||||
private _baseDir: string | null;
|
|
||||||
|
|
||||||
constructor(harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) {
|
|
||||||
this._harFile = harFile;
|
|
||||||
this._baseDir = baseDir;
|
|
||||||
this._zipFile = zipFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
async lookup(url: string, method: string, headers: HeadersArray, postData: Buffer | undefined, isNavigationRequest: boolean): Promise<{
|
|
||||||
action: 'error' | 'redirect' | 'fulfill' | 'noentry',
|
|
||||||
message?: string,
|
|
||||||
redirectURL?: string,
|
|
||||||
status?: number,
|
|
||||||
headers?: HeadersArray,
|
|
||||||
body?: Buffer }> {
|
|
||||||
let entry;
|
|
||||||
try {
|
|
||||||
entry = await this._harFindResponse(url, method, headers, postData);
|
|
||||||
} catch (e) {
|
|
||||||
return { action: 'error', message: 'HAR error: ' + e.message };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!entry)
|
|
||||||
return { action: 'noentry' };
|
|
||||||
|
|
||||||
// If navigation is being redirected, restart it with the final url to ensure the document's url changes.
|
|
||||||
if (entry.request.url !== url && isNavigationRequest)
|
|
||||||
return { action: 'redirect', redirectURL: entry.request.url };
|
|
||||||
|
|
||||||
const response = entry.response;
|
|
||||||
try {
|
|
||||||
const buffer = await this._loadContent(response.content);
|
|
||||||
return {
|
|
||||||
action: 'fulfill',
|
|
||||||
status: response.status,
|
|
||||||
headers: response.headers,
|
|
||||||
body: buffer,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
return { action: 'error', message: e.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadContent(content: { text?: string, encoding?: string, _file?: string }): Promise<Buffer> {
|
|
||||||
const file = content._file;
|
|
||||||
let buffer: Buffer;
|
|
||||||
if (file) {
|
|
||||||
if (this._zipFile)
|
|
||||||
buffer = await this._zipFile.read(file);
|
|
||||||
else
|
|
||||||
buffer = await fs.promises.readFile(path.resolve(this._baseDir!, file));
|
|
||||||
} else {
|
|
||||||
buffer = Buffer.from(content.text || '', content.encoding === 'base64' ? 'base64' : 'utf-8');
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _harFindResponse(url: string, method: string, headers: HeadersArray, postData: Buffer | undefined): Promise<har.Entry | undefined> {
|
|
||||||
const harLog = this._harFile.log;
|
|
||||||
const visited = new Set<har.Entry>();
|
|
||||||
while (true) {
|
|
||||||
const entries: har.Entry[] = [];
|
|
||||||
for (const candidate of harLog.entries) {
|
|
||||||
if (candidate.request.url !== url || candidate.request.method !== method)
|
|
||||||
continue;
|
|
||||||
if (method === 'POST' && postData && candidate.request.postData) {
|
|
||||||
const buffer = await this._loadContent(candidate.request.postData);
|
|
||||||
if (!buffer.equals(postData)) {
|
|
||||||
const boundary = multipartBoundary(headers);
|
|
||||||
if (!boundary)
|
|
||||||
continue;
|
|
||||||
const candidataBoundary = multipartBoundary(candidate.request.headers);
|
|
||||||
if (!candidataBoundary)
|
|
||||||
continue;
|
|
||||||
// Try to match multipart/form-data ignroing boundary as it changes between requests.
|
|
||||||
if (postData.toString().replaceAll(boundary, '') !== buffer.toString().replaceAll(candidataBoundary, ''))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entries.push(candidate);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!entries.length)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let entry = entries[0];
|
|
||||||
|
|
||||||
// Disambiguate using headers - then one with most matching headers wins.
|
|
||||||
if (entries.length > 1) {
|
|
||||||
const list: { candidate: har.Entry, matchingHeaders: number }[] = [];
|
|
||||||
for (const candidate of entries) {
|
|
||||||
const matchingHeaders = countMatchingHeaders(candidate.request.headers, headers);
|
|
||||||
list.push({ candidate, matchingHeaders });
|
|
||||||
}
|
|
||||||
list.sort((a, b) => b.matchingHeaders - a.matchingHeaders);
|
|
||||||
entry = list[0].candidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visited.has(entry))
|
|
||||||
throw new Error(`Found redirect cycle for ${url}`);
|
|
||||||
|
|
||||||
visited.add(entry);
|
|
||||||
|
|
||||||
// Follow redirects.
|
|
||||||
const locationHeader = entry.response.headers.find(h => h.name.toLowerCase() === 'location');
|
|
||||||
if (redirectStatus.includes(entry.response.status) && locationHeader) {
|
|
||||||
const locationURL = new URL(locationHeader.value, url);
|
|
||||||
url = locationURL.toString();
|
|
||||||
if ((entry.response.status === 301 || entry.response.status === 302) && method === 'POST' ||
|
|
||||||
entry.response.status === 303 && !['GET', 'HEAD'].includes(method)) {
|
|
||||||
// HTTP-redirect fetch step 13 (https://fetch.spec.whatwg.org/#http-redirect-fetch)
|
|
||||||
method = 'GET';
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
|
||||||
this._zipFile?.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function countMatchingHeaders(harHeaders: har.Header[], headers: HeadersArray): number {
|
|
||||||
const set = new Set(headers.map(h => h.name.toLowerCase() + ':' + h.value));
|
|
||||||
let matches = 0;
|
|
||||||
for (const h of harHeaders) {
|
|
||||||
if (set.has(h.name.toLowerCase() + ':' + h.value))
|
|
||||||
++matches;
|
|
||||||
}
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function urlToWSEndpoint(progress: Progress|undefined, endpointURL: string): Promise<string> {
|
|
||||||
if (endpointURL.startsWith('ws'))
|
|
||||||
return endpointURL;
|
|
||||||
|
|
||||||
progress?.log(`<ws preparing> retrieving websocket url from ${endpointURL}`);
|
|
||||||
const fetchUrl = new URL(endpointURL);
|
|
||||||
if (!fetchUrl.pathname.endsWith('/'))
|
|
||||||
fetchUrl.pathname += '/';
|
|
||||||
fetchUrl.pathname += 'json';
|
|
||||||
const json = await fetchData({
|
|
||||||
url: fetchUrl.toString(),
|
|
||||||
method: 'GET',
|
|
||||||
timeout: progress?.timeUntilDeadline() ?? 30_000,
|
|
||||||
headers: { 'User-Agent': getUserAgent() },
|
|
||||||
}, async (params: HTTPRequestParams, response: http.IncomingMessage) => {
|
|
||||||
return new Error(`Unexpected status ${response.statusCode} when connecting to ${fetchUrl.toString()}.\n` +
|
|
||||||
`This does not look like a Playwright server, try connecting via ws://.`);
|
|
||||||
});
|
|
||||||
progress?.throwIfAborted();
|
|
||||||
|
|
||||||
const wsUrl = new URL(endpointURL);
|
|
||||||
let wsEndpointPath = JSON.parse(json).wsEndpointPath;
|
|
||||||
if (wsEndpointPath.startsWith('/'))
|
|
||||||
wsEndpointPath = wsEndpointPath.substring(1);
|
|
||||||
if (!wsUrl.pathname.endsWith('/'))
|
|
||||||
wsUrl.pathname += '/';
|
|
||||||
wsUrl.pathname += wsEndpointPath;
|
|
||||||
wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
||||||
return wsUrl.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
function multipartBoundary(headers: HeadersArray) {
|
|
||||||
const contentType = headers.find(h => h.name.toLowerCase() === 'content-type');
|
|
||||||
if (!contentType?.value.includes('multipart/form-data'))
|
|
||||||
return undefined;
|
|
||||||
const boundary = contentType.value.match(/boundary=(\S+)/);
|
|
||||||
if (boundary)
|
|
||||||
return boundary[1];
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import { TimeoutSettings } from '../../common/timeoutSettings';
|
||||||
import { ManualPromise, wrapInASCIIBox } from '../../utils';
|
import { ManualPromise, wrapInASCIIBox } from '../../utils';
|
||||||
import { RecentLogsCollector } from '../../utils/debugLogger';
|
import { RecentLogsCollector } from '../../utils/debugLogger';
|
||||||
import { eventsHelper } from '../../utils/eventsHelper';
|
import { eventsHelper } from '../../utils/eventsHelper';
|
||||||
import { envArrayToObject, launchProcess } from '../../utils/processLauncher';
|
|
||||||
import { validateBrowserContextOptions } from '../browserContext';
|
import { validateBrowserContextOptions } from '../browserContext';
|
||||||
import { CRBrowser } from '../chromium/crBrowser';
|
import { CRBrowser } from '../chromium/crBrowser';
|
||||||
import { CRConnection } from '../chromium/crConnection';
|
import { CRConnection } from '../chromium/crConnection';
|
||||||
|
|
@ -33,6 +32,7 @@ import { ConsoleMessage } from '../console';
|
||||||
import { helper } from '../helper';
|
import { helper } from '../helper';
|
||||||
import { SdkObject, serverSideCallMetadata } from '../instrumentation';
|
import { SdkObject, serverSideCallMetadata } from '../instrumentation';
|
||||||
import * as js from '../javascript';
|
import * as js from '../javascript';
|
||||||
|
import { envArrayToObject, launchProcess } from '../processLauncher';
|
||||||
import { ProgressController } from '../progress';
|
import { ProgressController } from '../progress';
|
||||||
import { WebSocketTransport } from '../transport';
|
import { WebSocketTransport } from '../transport';
|
||||||
|
|
||||||
|
|
|
||||||
205
packages/playwright-core/src/server/fileUtils.ts
Normal file
205
packages/playwright-core/src/server/fileUtils.ts
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import { ManualPromise } from '../utils/manualPromise';
|
||||||
|
import { yazl } from '../zipBundle';
|
||||||
|
|
||||||
|
import type { EventEmitter } from 'events';
|
||||||
|
|
||||||
|
export const existsAsync = (path: string): Promise<boolean> => new Promise(resolve => fs.stat(path, err => resolve(!err)));
|
||||||
|
|
||||||
|
export async function mkdirIfNeeded(filePath: string) {
|
||||||
|
// This will harmlessly throw on windows if the dirname is the root directory.
|
||||||
|
await fs.promises.mkdir(path.dirname(filePath), { recursive: true }).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeFolders(dirs: string[]): Promise<Error[]> {
|
||||||
|
return await Promise.all(dirs.map((dir: string) =>
|
||||||
|
fs.promises.rm(dir, { recursive: true, force: true, maxRetries: 10 }).catch(e => e)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canAccessFile(file: string) {
|
||||||
|
if (!file)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.accessSync(file);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function copyFileAndMakeWritable(from: string, to: string) {
|
||||||
|
await fs.promises.copyFile(from, to);
|
||||||
|
await fs.promises.chmod(to, 0o664);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sanitizeForFilePath(s: string) {
|
||||||
|
return s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toPosixPath(aPath: string): string {
|
||||||
|
return aPath.split(path.sep).join(path.posix.sep);
|
||||||
|
}
|
||||||
|
|
||||||
|
type NameValue = { name: string, value: string };
|
||||||
|
type SerializedFSOperation = {
|
||||||
|
op: 'mkdir', dir: string,
|
||||||
|
} | {
|
||||||
|
op: 'writeFile', file: string, content: string | Buffer, skipIfExists?: boolean,
|
||||||
|
} | {
|
||||||
|
op: 'appendFile', file: string, content: string,
|
||||||
|
} | {
|
||||||
|
op: 'copyFile', from: string, to: string,
|
||||||
|
} | {
|
||||||
|
op: 'zip', entries: NameValue[], zipFileName: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SerializedFS {
|
||||||
|
private _buffers = new Map<string, string[]>(); // Should never be accessed from within appendOperation.
|
||||||
|
private _error: Error | undefined;
|
||||||
|
private _operations: SerializedFSOperation[] = [];
|
||||||
|
private _operationsDone: ManualPromise<void>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._operationsDone = new ManualPromise();
|
||||||
|
this._operationsDone.resolve(); // No operations scheduled yet.
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir(dir: string) {
|
||||||
|
this._appendOperation({ op: 'mkdir', dir });
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFile(file: string, content: string | Buffer, skipIfExists?: boolean) {
|
||||||
|
this._buffers.delete(file); // No need to flush the buffer since we'll overwrite anyway.
|
||||||
|
this._appendOperation({ op: 'writeFile', file, content, skipIfExists });
|
||||||
|
}
|
||||||
|
|
||||||
|
appendFile(file: string, text: string, flush?: boolean) {
|
||||||
|
if (!this._buffers.has(file))
|
||||||
|
this._buffers.set(file, []);
|
||||||
|
this._buffers.get(file)!.push(text);
|
||||||
|
if (flush)
|
||||||
|
this._flushFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _flushFile(file: string) {
|
||||||
|
const buffer = this._buffers.get(file);
|
||||||
|
if (buffer === undefined)
|
||||||
|
return;
|
||||||
|
const content = buffer.join('');
|
||||||
|
this._buffers.delete(file);
|
||||||
|
this._appendOperation({ op: 'appendFile', file, content });
|
||||||
|
}
|
||||||
|
|
||||||
|
copyFile(from: string, to: string) {
|
||||||
|
this._flushFile(from);
|
||||||
|
this._buffers.delete(to); // No need to flush the buffer since we'll overwrite anyway.
|
||||||
|
this._appendOperation({ op: 'copyFile', from, to });
|
||||||
|
}
|
||||||
|
|
||||||
|
async syncAndGetError() {
|
||||||
|
for (const file of this._buffers.keys())
|
||||||
|
this._flushFile(file);
|
||||||
|
await this._operationsDone;
|
||||||
|
return this._error;
|
||||||
|
}
|
||||||
|
|
||||||
|
zip(entries: NameValue[], zipFileName: string) {
|
||||||
|
for (const file of this._buffers.keys())
|
||||||
|
this._flushFile(file);
|
||||||
|
|
||||||
|
// Chain the export operation against write operations,
|
||||||
|
// so that files do not change during the export.
|
||||||
|
this._appendOperation({ op: 'zip', entries, zipFileName });
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method serializes all writes to the trace.
|
||||||
|
private _appendOperation(op: SerializedFSOperation): void {
|
||||||
|
const last = this._operations[this._operations.length - 1];
|
||||||
|
if (last?.op === 'appendFile' && op.op === 'appendFile' && last.file === op.file) {
|
||||||
|
// Merge pending appendFile operations for performance.
|
||||||
|
last.content += op.content;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._operations.push(op);
|
||||||
|
if (this._operationsDone.isDone())
|
||||||
|
this._performOperations();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _performOperations() {
|
||||||
|
this._operationsDone = new ManualPromise();
|
||||||
|
while (this._operations.length) {
|
||||||
|
const op = this._operations.shift()!;
|
||||||
|
// Ignore all operations after the first error.
|
||||||
|
if (this._error)
|
||||||
|
continue;
|
||||||
|
try {
|
||||||
|
await this._performOperation(op);
|
||||||
|
} catch (e) {
|
||||||
|
this._error = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._operationsDone.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _performOperation(op: SerializedFSOperation) {
|
||||||
|
switch (op.op) {
|
||||||
|
case 'mkdir': {
|
||||||
|
await fs.promises.mkdir(op.dir, { recursive: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'writeFile': {
|
||||||
|
// Note: 'wx' flag only writes when the file does not exist.
|
||||||
|
// See https://nodejs.org/api/fs.html#file-system-flags.
|
||||||
|
// This way tracing never have to write the same resource twice.
|
||||||
|
if (op.skipIfExists)
|
||||||
|
await fs.promises.writeFile(op.file, op.content, { flag: 'wx' }).catch(() => {});
|
||||||
|
else
|
||||||
|
await fs.promises.writeFile(op.file, op.content);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'copyFile': {
|
||||||
|
await fs.promises.copyFile(op.from, op.to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'appendFile': {
|
||||||
|
await fs.promises.appendFile(op.file, op.content);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'zip': {
|
||||||
|
const zipFile = new yazl.ZipFile();
|
||||||
|
const result = new ManualPromise<void>();
|
||||||
|
(zipFile as any as EventEmitter).on('error', error => result.reject(error));
|
||||||
|
for (const entry of op.entries)
|
||||||
|
zipFile.addFile(entry.value, entry.name);
|
||||||
|
zipFile.end();
|
||||||
|
zipFile.outputStream
|
||||||
|
.pipe(fs.createWriteStream(op.zipFileName))
|
||||||
|
.on('close', () => result.resolve())
|
||||||
|
.on('error', error => result.reject(error));
|
||||||
|
await result;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,9 +24,9 @@ import { wrapInASCIIBox } from '../../utils';
|
||||||
import { BrowserType, kNoXServerRunningError } from '../browserType';
|
import { BrowserType, kNoXServerRunningError } from '../browserType';
|
||||||
import { BrowserReadyState } from '../browserType';
|
import { BrowserReadyState } from '../browserType';
|
||||||
|
|
||||||
import type { Env } from '../../utils/processLauncher';
|
|
||||||
import type { BrowserOptions } from '../browser';
|
import type { BrowserOptions } from '../browser';
|
||||||
import type { SdkObject } from '../instrumentation';
|
import type { SdkObject } from '../instrumentation';
|
||||||
|
import type { Env } from '../processLauncher';
|
||||||
import type { ProtocolError } from '../protocolError';
|
import type { ProtocolError } from '../protocolError';
|
||||||
import type { ConnectionTransport } from '../transport';
|
import type { ConnectionTransport } from '../transport';
|
||||||
import type * as types from '../types';
|
import type * as types from '../types';
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,5 @@ export type { Playwright } from './playwright';
|
||||||
export { installRootRedirect, openTraceInBrowser, openTraceViewerApp, runTraceViewerApp, startTraceViewerServer } from './trace/viewer/traceViewer';
|
export { installRootRedirect, openTraceInBrowser, openTraceViewerApp, runTraceViewerApp, startTraceViewerServer } from './trace/viewer/traceViewer';
|
||||||
export { serverSideCallMetadata } from './instrumentation';
|
export { serverSideCallMetadata } from './instrumentation';
|
||||||
export { SocksProxy } from '../common/socksProxy';
|
export { SocksProxy } from '../common/socksProxy';
|
||||||
|
export * from './fileUtils';
|
||||||
|
export * from './processLauncher';
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,7 @@ import * as fs from 'fs';
|
||||||
import * as readline from 'readline';
|
import * as readline from 'readline';
|
||||||
|
|
||||||
import { removeFolders } from './fileUtils';
|
import { removeFolders } from './fileUtils';
|
||||||
|
import { isUnderTest } from '../utils';
|
||||||
import { isUnderTest } from './';
|
|
||||||
|
|
||||||
export type Env = {[key: string]: string | number | boolean | undefined};
|
export type Env = {[key: string]: string | number | boolean | undefined};
|
||||||
|
|
||||||
|
|
@ -19,14 +19,10 @@ import { assert, monotonicTime } from '../utils';
|
||||||
import { ManualPromise } from '../utils/manualPromise';
|
import { ManualPromise } from '../utils/manualPromise';
|
||||||
|
|
||||||
import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation';
|
import type { CallMetadata, Instrumentation, SdkObject } from './instrumentation';
|
||||||
|
import type { Progress as CommonProgress } from '../common/progress';
|
||||||
import type { LogName } from '../utils/debugLogger';
|
import type { LogName } from '../utils/debugLogger';
|
||||||
|
|
||||||
export interface Progress {
|
export interface Progress extends CommonProgress {
|
||||||
log(message: string): void;
|
|
||||||
timeUntilDeadline(): number;
|
|
||||||
isRunning(): boolean;
|
|
||||||
cleanupWhenAborted(cleanup: () => any): void;
|
|
||||||
throwIfAborted(): void;
|
|
||||||
metadata: CallMetadata;
|
metadata: CallMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { debugLogger } from '../../utils/debugLogger';
|
import { debugLogger } from '../../utils/debugLogger';
|
||||||
import { existsAsync } from '../../utils/fileUtils';
|
|
||||||
import { ManualPromise } from '../../utils/manualPromise';
|
import { ManualPromise } from '../../utils/manualPromise';
|
||||||
import { getUserAgent } from '../../utils/userAgent';
|
import { getUserAgent } from '../../utils/userAgent';
|
||||||
import { colors, progress as ProgressBar } from '../../utilsBundle';
|
import { colors, progress as ProgressBar } from '../../utilsBundle';
|
||||||
|
import { existsAsync } from '../fileUtils';
|
||||||
|
|
||||||
import { browserDirectoryToMarkerFilePath } from '.';
|
import { browserDirectoryToMarkerFilePath } from '.';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,12 @@ import { dockerVersion, readDockerVersionSync, transformCommandsForRoot } from '
|
||||||
import { installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies';
|
import { installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies';
|
||||||
import { calculateSha1, getAsBooleanFromENV, getFromENV, getPackageManagerExecCommand, wrapInASCIIBox } from '../../utils';
|
import { calculateSha1, getAsBooleanFromENV, getFromENV, getPackageManagerExecCommand, wrapInASCIIBox } from '../../utils';
|
||||||
import { debugLogger } from '../../utils/debugLogger';
|
import { debugLogger } from '../../utils/debugLogger';
|
||||||
import { canAccessFile, existsAsync, removeFolders } from '../../utils/fileUtils';
|
|
||||||
import { hostPlatform, isOfficiallySupportedPlatform } from '../../utils/hostPlatform';
|
import { hostPlatform, isOfficiallySupportedPlatform } from '../../utils/hostPlatform';
|
||||||
import { fetchData } from '../../utils/network';
|
import { fetchData } from '../../utils/network';
|
||||||
import { spawnAsync } from '../../utils/spawnAsync';
|
import { spawnAsync } from '../../utils/spawnAsync';
|
||||||
import { getEmbedderName } from '../../utils/userAgent';
|
import { getEmbedderName } from '../../utils/userAgent';
|
||||||
import { lockfile } from '../../utilsBundle';
|
import { lockfile } from '../../utilsBundle';
|
||||||
|
import { canAccessFile, existsAsync, removeFolders } from '../fileUtils';
|
||||||
|
|
||||||
import type { DependencyGroup } from './dependencies';
|
import type { DependencyGroup } from './dependencies';
|
||||||
import type { HostPlatform } from '../../utils/hostPlatform';
|
import type { HostPlatform } from '../../utils/hostPlatform';
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,12 @@ import * as path from 'path';
|
||||||
|
|
||||||
import { Snapshotter } from './snapshotter';
|
import { Snapshotter } from './snapshotter';
|
||||||
import { commandsWithTracingSnapshots } from '../../../protocol/debug';
|
import { commandsWithTracingSnapshots } from '../../../protocol/debug';
|
||||||
import { SerializedFS, assert, createGuid, eventsHelper, monotonicTime, removeFolders } from '../../../utils';
|
import { assert, createGuid, eventsHelper, monotonicTime } from '../../../utils';
|
||||||
import { Artifact } from '../../artifact';
|
import { Artifact } from '../../artifact';
|
||||||
import { BrowserContext } from '../../browserContext';
|
import { BrowserContext } from '../../browserContext';
|
||||||
import { Dispatcher } from '../../dispatchers/dispatcher';
|
import { Dispatcher } from '../../dispatchers/dispatcher';
|
||||||
import { serializeError } from '../../errors';
|
import { serializeError } from '../../errors';
|
||||||
|
import { SerializedFS, removeFolders } from '../../fileUtils';
|
||||||
import { HarTracer } from '../../har/harTracer';
|
import { HarTracer } from '../../har/harTracer';
|
||||||
import { SdkObject } from '../../instrumentation';
|
import { SdkObject } from '../../instrumentation';
|
||||||
import { Page } from '../../page';
|
import { Page } from '../../page';
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { gracefullyProcessExitDoNotHang, isUnderTest } from '../../../utils';
|
import { gracefullyProcessExitDoNotHang } from '../../../server';
|
||||||
|
import { isUnderTest } from '../../../utils';
|
||||||
import { HttpServer } from '../../../utils/httpServer';
|
import { HttpServer } from '../../../utils/httpServer';
|
||||||
import { open } from '../../../utilsBundle';
|
import { open } from '../../../utilsBundle';
|
||||||
import { serverSideCallMetadata } from '../../instrumentation';
|
import { serverSideCallMetadata } from '../../instrumentation';
|
||||||
|
|
|
||||||
|
|
@ -7781,6 +7781,18 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the
|
||||||
}
|
}
|
||||||
export type setIgnoreCertificateErrorsReturnValue = {
|
export type setIgnoreCertificateErrorsReturnValue = {
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Changes page zoom factor.
|
||||||
|
*/
|
||||||
|
export type setPageZoomFactorParameters = {
|
||||||
|
/**
|
||||||
|
* Unique identifier of the page proxy.
|
||||||
|
*/
|
||||||
|
pageProxyId: PageProxyID;
|
||||||
|
zoomFactor: number;
|
||||||
|
}
|
||||||
|
export type setPageZoomFactorReturnValue = {
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Returns all cookies in the given browser context.
|
* Returns all cookies in the given browser context.
|
||||||
*/
|
*/
|
||||||
|
|
@ -9658,6 +9670,7 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the
|
||||||
"Playwright.grantFileReadAccess": Playwright.grantFileReadAccessParameters;
|
"Playwright.grantFileReadAccess": Playwright.grantFileReadAccessParameters;
|
||||||
"Playwright.takePageScreenshot": Playwright.takePageScreenshotParameters;
|
"Playwright.takePageScreenshot": Playwright.takePageScreenshotParameters;
|
||||||
"Playwright.setIgnoreCertificateErrors": Playwright.setIgnoreCertificateErrorsParameters;
|
"Playwright.setIgnoreCertificateErrors": Playwright.setIgnoreCertificateErrorsParameters;
|
||||||
|
"Playwright.setPageZoomFactor": Playwright.setPageZoomFactorParameters;
|
||||||
"Playwright.getAllCookies": Playwright.getAllCookiesParameters;
|
"Playwright.getAllCookies": Playwright.getAllCookiesParameters;
|
||||||
"Playwright.setCookies": Playwright.setCookiesParameters;
|
"Playwright.setCookies": Playwright.setCookiesParameters;
|
||||||
"Playwright.deleteAllCookies": Playwright.deleteAllCookiesParameters;
|
"Playwright.deleteAllCookies": Playwright.deleteAllCookiesParameters;
|
||||||
|
|
@ -9970,6 +9983,7 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the
|
||||||
"Playwright.grantFileReadAccess": Playwright.grantFileReadAccessReturnValue;
|
"Playwright.grantFileReadAccess": Playwright.grantFileReadAccessReturnValue;
|
||||||
"Playwright.takePageScreenshot": Playwright.takePageScreenshotReturnValue;
|
"Playwright.takePageScreenshot": Playwright.takePageScreenshotReturnValue;
|
||||||
"Playwright.setIgnoreCertificateErrors": Playwright.setIgnoreCertificateErrorsReturnValue;
|
"Playwright.setIgnoreCertificateErrors": Playwright.setIgnoreCertificateErrorsReturnValue;
|
||||||
|
"Playwright.setPageZoomFactor": Playwright.setPageZoomFactorReturnValue;
|
||||||
"Playwright.getAllCookies": Playwright.getAllCookiesReturnValue;
|
"Playwright.getAllCookies": Playwright.getAllCookiesReturnValue;
|
||||||
"Playwright.setCookies": Playwright.setCookiesReturnValue;
|
"Playwright.setCookies": Playwright.setCookiesReturnValue;
|
||||||
"Playwright.deleteAllCookies": Playwright.deleteAllCookiesReturnValue;
|
"Playwright.deleteAllCookies": Playwright.deleteAllCookiesReturnValue;
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ import { wrapInASCIIBox } from '../../utils';
|
||||||
import { BrowserType, kNoXServerRunningError } from '../browserType';
|
import { BrowserType, kNoXServerRunningError } from '../browserType';
|
||||||
import { WKBrowser } from '../webkit/wkBrowser';
|
import { WKBrowser } from '../webkit/wkBrowser';
|
||||||
|
|
||||||
import type { Env } from '../../utils/processLauncher';
|
|
||||||
import type { BrowserOptions } from '../browser';
|
import type { BrowserOptions } from '../browser';
|
||||||
import type { SdkObject } from '../instrumentation';
|
import type { SdkObject } from '../instrumentation';
|
||||||
|
import type { Env } from '../processLauncher';
|
||||||
import type { ProtocolError } from '../protocolError';
|
import type { ProtocolError } from '../protocolError';
|
||||||
import type { ConnectionTransport } from '../transport';
|
import type { ConnectionTransport } from '../transport';
|
||||||
import type * as types from '../types';
|
import type * as types from '../types';
|
||||||
|
|
|
||||||
|
|
@ -14,194 +14,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import type { Platform } from './platform';
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
import { ManualPromise } from './manualPromise';
|
|
||||||
import { yazl } from '../zipBundle';
|
|
||||||
|
|
||||||
import type { EventEmitter } from 'events';
|
|
||||||
|
|
||||||
export const fileUploadSizeLimit = 50 * 1024 * 1024;
|
export const fileUploadSizeLimit = 50 * 1024 * 1024;
|
||||||
|
|
||||||
export const existsAsync = (path: string): Promise<boolean> => new Promise(resolve => fs.stat(path, err => resolve(!err)));
|
export async function mkdirIfNeeded(platform: Platform, filePath: string) {
|
||||||
|
|
||||||
export async function mkdirIfNeeded(filePath: string) {
|
|
||||||
// This will harmlessly throw on windows if the dirname is the root directory.
|
// This will harmlessly throw on windows if the dirname is the root directory.
|
||||||
await fs.promises.mkdir(path.dirname(filePath), { recursive: true }).catch(() => {});
|
await platform.fs().promises.mkdir(platform.path().dirname(filePath), { recursive: true }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeFolders(dirs: string[]): Promise<Error[]> {
|
export async function removeFolders(platform: Platform, dirs: string[]): Promise<Error[]> {
|
||||||
return await Promise.all(dirs.map((dir: string) =>
|
return await Promise.all(dirs.map((dir: string) =>
|
||||||
fs.promises.rm(dir, { recursive: true, force: true, maxRetries: 10 }).catch(e => e)
|
platform.fs().promises.rm(dir, { recursive: true, force: true, maxRetries: 10 }).catch(e => e)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canAccessFile(file: string) {
|
|
||||||
if (!file)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.accessSync(file);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function copyFileAndMakeWritable(from: string, to: string) {
|
|
||||||
await fs.promises.copyFile(from, to);
|
|
||||||
await fs.promises.chmod(to, 0o664);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sanitizeForFilePath(s: string) {
|
|
||||||
return s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toPosixPath(aPath: string): string {
|
|
||||||
return aPath.split(path.sep).join(path.posix.sep);
|
|
||||||
}
|
|
||||||
|
|
||||||
type NameValue = { name: string, value: string };
|
|
||||||
type SerializedFSOperation = {
|
|
||||||
op: 'mkdir', dir: string,
|
|
||||||
} | {
|
|
||||||
op: 'writeFile', file: string, content: string | Buffer, skipIfExists?: boolean,
|
|
||||||
} | {
|
|
||||||
op: 'appendFile', file: string, content: string,
|
|
||||||
} | {
|
|
||||||
op: 'copyFile', from: string, to: string,
|
|
||||||
} | {
|
|
||||||
op: 'zip', entries: NameValue[], zipFileName: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export class SerializedFS {
|
|
||||||
private _buffers = new Map<string, string[]>(); // Should never be accessed from within appendOperation.
|
|
||||||
private _error: Error | undefined;
|
|
||||||
private _operations: SerializedFSOperation[] = [];
|
|
||||||
private _operationsDone: ManualPromise<void>;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this._operationsDone = new ManualPromise();
|
|
||||||
this._operationsDone.resolve(); // No operations scheduled yet.
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdir(dir: string) {
|
|
||||||
this._appendOperation({ op: 'mkdir', dir });
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFile(file: string, content: string | Buffer, skipIfExists?: boolean) {
|
|
||||||
this._buffers.delete(file); // No need to flush the buffer since we'll overwrite anyway.
|
|
||||||
this._appendOperation({ op: 'writeFile', file, content, skipIfExists });
|
|
||||||
}
|
|
||||||
|
|
||||||
appendFile(file: string, text: string, flush?: boolean) {
|
|
||||||
if (!this._buffers.has(file))
|
|
||||||
this._buffers.set(file, []);
|
|
||||||
this._buffers.get(file)!.push(text);
|
|
||||||
if (flush)
|
|
||||||
this._flushFile(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _flushFile(file: string) {
|
|
||||||
const buffer = this._buffers.get(file);
|
|
||||||
if (buffer === undefined)
|
|
||||||
return;
|
|
||||||
const content = buffer.join('');
|
|
||||||
this._buffers.delete(file);
|
|
||||||
this._appendOperation({ op: 'appendFile', file, content });
|
|
||||||
}
|
|
||||||
|
|
||||||
copyFile(from: string, to: string) {
|
|
||||||
this._flushFile(from);
|
|
||||||
this._buffers.delete(to); // No need to flush the buffer since we'll overwrite anyway.
|
|
||||||
this._appendOperation({ op: 'copyFile', from, to });
|
|
||||||
}
|
|
||||||
|
|
||||||
async syncAndGetError() {
|
|
||||||
for (const file of this._buffers.keys())
|
|
||||||
this._flushFile(file);
|
|
||||||
await this._operationsDone;
|
|
||||||
return this._error;
|
|
||||||
}
|
|
||||||
|
|
||||||
zip(entries: NameValue[], zipFileName: string) {
|
|
||||||
for (const file of this._buffers.keys())
|
|
||||||
this._flushFile(file);
|
|
||||||
|
|
||||||
// Chain the export operation against write operations,
|
|
||||||
// so that files do not change during the export.
|
|
||||||
this._appendOperation({ op: 'zip', entries, zipFileName });
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method serializes all writes to the trace.
|
|
||||||
private _appendOperation(op: SerializedFSOperation): void {
|
|
||||||
const last = this._operations[this._operations.length - 1];
|
|
||||||
if (last?.op === 'appendFile' && op.op === 'appendFile' && last.file === op.file) {
|
|
||||||
// Merge pending appendFile operations for performance.
|
|
||||||
last.content += op.content;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._operations.push(op);
|
|
||||||
if (this._operationsDone.isDone())
|
|
||||||
this._performOperations();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _performOperations() {
|
|
||||||
this._operationsDone = new ManualPromise();
|
|
||||||
while (this._operations.length) {
|
|
||||||
const op = this._operations.shift()!;
|
|
||||||
// Ignore all operations after the first error.
|
|
||||||
if (this._error)
|
|
||||||
continue;
|
|
||||||
try {
|
|
||||||
await this._performOperation(op);
|
|
||||||
} catch (e) {
|
|
||||||
this._error = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._operationsDone.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _performOperation(op: SerializedFSOperation) {
|
|
||||||
switch (op.op) {
|
|
||||||
case 'mkdir': {
|
|
||||||
await fs.promises.mkdir(op.dir, { recursive: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case 'writeFile': {
|
|
||||||
// Note: 'wx' flag only writes when the file does not exist.
|
|
||||||
// See https://nodejs.org/api/fs.html#file-system-flags.
|
|
||||||
// This way tracing never have to write the same resource twice.
|
|
||||||
if (op.skipIfExists)
|
|
||||||
await fs.promises.writeFile(op.file, op.content, { flag: 'wx' }).catch(() => {});
|
|
||||||
else
|
|
||||||
await fs.promises.writeFile(op.file, op.content);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case 'copyFile': {
|
|
||||||
await fs.promises.copyFile(op.from, op.to);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case 'appendFile': {
|
|
||||||
await fs.promises.appendFile(op.file, op.content);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case 'zip': {
|
|
||||||
const zipFile = new yazl.ZipFile();
|
|
||||||
const result = new ManualPromise<void>();
|
|
||||||
(zipFile as any as EventEmitter).on('error', error => result.reject(error));
|
|
||||||
for (const entry of op.entries)
|
|
||||||
zipFile.addFile(entry.value, entry.name);
|
|
||||||
zipFile.end();
|
|
||||||
zipFile.outputStream
|
|
||||||
.pipe(fs.createWriteStream(op.zipFileName))
|
|
||||||
.on('close', () => result.resolve())
|
|
||||||
.on('error', error => result.reject(error));
|
|
||||||
await result;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
175
packages/playwright-core/src/utils/harBackend.ts
Normal file
175
packages/playwright-core/src/utils/harBackend.ts
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the 'License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import { createGuid } from './crypto';
|
||||||
|
import { ZipFile } from './zipFile';
|
||||||
|
|
||||||
|
import type { HeadersArray } from '../common/types';
|
||||||
|
import type * as har from '@trace/har';
|
||||||
|
|
||||||
|
const redirectStatus = [301, 302, 303, 307, 308];
|
||||||
|
|
||||||
|
export class HarBackend {
|
||||||
|
readonly id = createGuid();
|
||||||
|
private _harFile: har.HARFile;
|
||||||
|
private _zipFile: ZipFile | null;
|
||||||
|
private _baseDir: string | null;
|
||||||
|
|
||||||
|
constructor(harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) {
|
||||||
|
this._harFile = harFile;
|
||||||
|
this._baseDir = baseDir;
|
||||||
|
this._zipFile = zipFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async lookup(url: string, method: string, headers: HeadersArray, postData: Buffer | undefined, isNavigationRequest: boolean): Promise<{
|
||||||
|
action: 'error' | 'redirect' | 'fulfill' | 'noentry',
|
||||||
|
message?: string,
|
||||||
|
redirectURL?: string,
|
||||||
|
status?: number,
|
||||||
|
headers?: HeadersArray,
|
||||||
|
body?: Buffer }> {
|
||||||
|
let entry;
|
||||||
|
try {
|
||||||
|
entry = await this._harFindResponse(url, method, headers, postData);
|
||||||
|
} catch (e) {
|
||||||
|
return { action: 'error', message: 'HAR error: ' + e.message };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entry)
|
||||||
|
return { action: 'noentry' };
|
||||||
|
|
||||||
|
// If navigation is being redirected, restart it with the final url to ensure the document's url changes.
|
||||||
|
if (entry.request.url !== url && isNavigationRequest)
|
||||||
|
return { action: 'redirect', redirectURL: entry.request.url };
|
||||||
|
|
||||||
|
const response = entry.response;
|
||||||
|
try {
|
||||||
|
const buffer = await this._loadContent(response.content);
|
||||||
|
return {
|
||||||
|
action: 'fulfill',
|
||||||
|
status: response.status,
|
||||||
|
headers: response.headers,
|
||||||
|
body: buffer,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return { action: 'error', message: e.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _loadContent(content: { text?: string, encoding?: string, _file?: string }): Promise<Buffer> {
|
||||||
|
const file = content._file;
|
||||||
|
let buffer: Buffer;
|
||||||
|
if (file) {
|
||||||
|
if (this._zipFile)
|
||||||
|
buffer = await this._zipFile.read(file);
|
||||||
|
else
|
||||||
|
buffer = await fs.promises.readFile(path.resolve(this._baseDir!, file));
|
||||||
|
} else {
|
||||||
|
buffer = Buffer.from(content.text || '', content.encoding === 'base64' ? 'base64' : 'utf-8');
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _harFindResponse(url: string, method: string, headers: HeadersArray, postData: Buffer | undefined): Promise<har.Entry | undefined> {
|
||||||
|
const harLog = this._harFile.log;
|
||||||
|
const visited = new Set<har.Entry>();
|
||||||
|
while (true) {
|
||||||
|
const entries: har.Entry[] = [];
|
||||||
|
for (const candidate of harLog.entries) {
|
||||||
|
if (candidate.request.url !== url || candidate.request.method !== method)
|
||||||
|
continue;
|
||||||
|
if (method === 'POST' && postData && candidate.request.postData) {
|
||||||
|
const buffer = await this._loadContent(candidate.request.postData);
|
||||||
|
if (!buffer.equals(postData)) {
|
||||||
|
const boundary = multipartBoundary(headers);
|
||||||
|
if (!boundary)
|
||||||
|
continue;
|
||||||
|
const candidataBoundary = multipartBoundary(candidate.request.headers);
|
||||||
|
if (!candidataBoundary)
|
||||||
|
continue;
|
||||||
|
// Try to match multipart/form-data ignroing boundary as it changes between requests.
|
||||||
|
if (postData.toString().replaceAll(boundary, '') !== buffer.toString().replaceAll(candidataBoundary, ''))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entries.push(candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entries.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let entry = entries[0];
|
||||||
|
|
||||||
|
// Disambiguate using headers - then one with most matching headers wins.
|
||||||
|
if (entries.length > 1) {
|
||||||
|
const list: { candidate: har.Entry, matchingHeaders: number }[] = [];
|
||||||
|
for (const candidate of entries) {
|
||||||
|
const matchingHeaders = countMatchingHeaders(candidate.request.headers, headers);
|
||||||
|
list.push({ candidate, matchingHeaders });
|
||||||
|
}
|
||||||
|
list.sort((a, b) => b.matchingHeaders - a.matchingHeaders);
|
||||||
|
entry = list[0].candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visited.has(entry))
|
||||||
|
throw new Error(`Found redirect cycle for ${url}`);
|
||||||
|
|
||||||
|
visited.add(entry);
|
||||||
|
|
||||||
|
// Follow redirects.
|
||||||
|
const locationHeader = entry.response.headers.find(h => h.name.toLowerCase() === 'location');
|
||||||
|
if (redirectStatus.includes(entry.response.status) && locationHeader) {
|
||||||
|
const locationURL = new URL(locationHeader.value, url);
|
||||||
|
url = locationURL.toString();
|
||||||
|
if ((entry.response.status === 301 || entry.response.status === 302) && method === 'POST' ||
|
||||||
|
entry.response.status === 303 && !['GET', 'HEAD'].includes(method)) {
|
||||||
|
// HTTP-redirect fetch step 13 (https://fetch.spec.whatwg.org/#http-redirect-fetch)
|
||||||
|
method = 'GET';
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._zipFile?.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function countMatchingHeaders(harHeaders: har.Header[], headers: HeadersArray): number {
|
||||||
|
const set = new Set(headers.map(h => h.name.toLowerCase() + ':' + h.value));
|
||||||
|
let matches = 0;
|
||||||
|
for (const h of harHeaders) {
|
||||||
|
if (set.has(h.name.toLowerCase() + ':' + h.value))
|
||||||
|
++matches;
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
function multipartBoundary(headers: HeadersArray) {
|
||||||
|
const contentType = headers.find(h => h.name.toLowerCase() === 'content-type');
|
||||||
|
if (!contentType?.value.includes('multipart/form-data'))
|
||||||
|
return undefined;
|
||||||
|
const boundary = contentType.value.match(/boundary=(\S+)/);
|
||||||
|
if (boundary)
|
||||||
|
return boundary[1];
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,6 @@ export * from './isomorphic/stringUtils';
|
||||||
export * from './isomorphic/urlMatch';
|
export * from './isomorphic/urlMatch';
|
||||||
export * from './multimap';
|
export * from './multimap';
|
||||||
export * from './network';
|
export * from './network';
|
||||||
export * from './processLauncher';
|
|
||||||
export * from './profiler';
|
export * from './profiler';
|
||||||
export * from './rtti';
|
export * from './rtti';
|
||||||
export * from './semaphore';
|
export * from './semaphore';
|
||||||
|
|
|
||||||
248
packages/playwright-core/src/utils/localUtils.ts
Normal file
248
packages/playwright-core/src/utils/localUtils.ts
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the 'License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as os from 'os';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import { removeFolders } from './fileUtils';
|
||||||
|
import { HarBackend } from './harBackend';
|
||||||
|
import { ManualPromise } from './manualPromise';
|
||||||
|
import { fetchData } from './network';
|
||||||
|
import { getUserAgent } from './userAgent';
|
||||||
|
import { ZipFile } from './zipFile';
|
||||||
|
import { yauzl, yazl } from '../zipBundle';
|
||||||
|
|
||||||
|
import { serializeClientSideCallMetadata } from '.';
|
||||||
|
import { assert, calculateSha1 } from '.';
|
||||||
|
|
||||||
|
import type { HTTPRequestParams } from './network';
|
||||||
|
import type { Platform } from './platform';
|
||||||
|
import type { Progress } from '../common/progress';
|
||||||
|
import type * as channels from '@protocol/channels';
|
||||||
|
import type * as har from '@trace/har';
|
||||||
|
import type EventEmitter from 'events';
|
||||||
|
import type http from 'http';
|
||||||
|
|
||||||
|
|
||||||
|
export type StackSession = {
|
||||||
|
file: string;
|
||||||
|
writer: Promise<void>;
|
||||||
|
tmpDir: string | undefined;
|
||||||
|
callStacks: channels.ClientSideCallMetadata[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function zip(platform: Platform, stackSessions: Map<string, StackSession>, params: channels.LocalUtilsZipParams): Promise<void> {
|
||||||
|
const promise = new ManualPromise<void>();
|
||||||
|
const zipFile = new yazl.ZipFile();
|
||||||
|
(zipFile as any as EventEmitter).on('error', error => promise.reject(error));
|
||||||
|
|
||||||
|
const addFile = (file: string, name: string) => {
|
||||||
|
try {
|
||||||
|
if (fs.statSync(file).isFile())
|
||||||
|
zipFile.addFile(file, name);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const entry of params.entries)
|
||||||
|
addFile(entry.value, entry.name);
|
||||||
|
|
||||||
|
// Add stacks and the sources.
|
||||||
|
const stackSession = params.stacksId ? stackSessions.get(params.stacksId) : undefined;
|
||||||
|
if (stackSession?.callStacks.length) {
|
||||||
|
await stackSession.writer;
|
||||||
|
if (process.env.PW_LIVE_TRACE_STACKS) {
|
||||||
|
zipFile.addFile(stackSession.file, 'trace.stacks');
|
||||||
|
} else {
|
||||||
|
const buffer = Buffer.from(JSON.stringify(serializeClientSideCallMetadata(stackSession.callStacks)));
|
||||||
|
zipFile.addBuffer(buffer, 'trace.stacks');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect sources from stacks.
|
||||||
|
if (params.includeSources) {
|
||||||
|
const sourceFiles = new Set<string>();
|
||||||
|
for (const { stack } of stackSession?.callStacks || []) {
|
||||||
|
if (!stack)
|
||||||
|
continue;
|
||||||
|
for (const { file } of stack)
|
||||||
|
sourceFiles.add(file);
|
||||||
|
}
|
||||||
|
for (const sourceFile of sourceFiles)
|
||||||
|
addFile(sourceFile, 'resources/src@' + calculateSha1(sourceFile) + '.txt');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.mode === 'write') {
|
||||||
|
// New file, just compress the entries.
|
||||||
|
await fs.promises.mkdir(path.dirname(params.zipFile), { recursive: true });
|
||||||
|
zipFile.end(undefined, () => {
|
||||||
|
zipFile.outputStream.pipe(fs.createWriteStream(params.zipFile))
|
||||||
|
.on('close', () => promise.resolve())
|
||||||
|
.on('error', error => promise.reject(error));
|
||||||
|
});
|
||||||
|
await promise;
|
||||||
|
await deleteStackSession(platform, stackSessions, params.stacksId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// File already exists. Repack and add new entries.
|
||||||
|
const tempFile = params.zipFile + '.tmp';
|
||||||
|
await fs.promises.rename(params.zipFile, tempFile);
|
||||||
|
|
||||||
|
yauzl.open(tempFile, (err, inZipFile) => {
|
||||||
|
if (err) {
|
||||||
|
promise.reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert(inZipFile);
|
||||||
|
let pendingEntries = inZipFile.entryCount;
|
||||||
|
inZipFile.on('entry', entry => {
|
||||||
|
inZipFile.openReadStream(entry, (err, readStream) => {
|
||||||
|
if (err) {
|
||||||
|
promise.reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zipFile.addReadStream(readStream!, entry.fileName);
|
||||||
|
if (--pendingEntries === 0) {
|
||||||
|
zipFile.end(undefined, () => {
|
||||||
|
zipFile.outputStream.pipe(fs.createWriteStream(params.zipFile)).on('close', () => {
|
||||||
|
fs.promises.unlink(tempFile).then(() => {
|
||||||
|
promise.resolve();
|
||||||
|
}).catch(error => promise.reject(error));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await promise;
|
||||||
|
await deleteStackSession(platform, stackSessions, params.stacksId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteStackSession(platform: Platform, stackSessions: Map<string, StackSession>, stacksId?: string) {
|
||||||
|
const session = stacksId ? stackSessions.get(stacksId) : undefined;
|
||||||
|
if (!session)
|
||||||
|
return;
|
||||||
|
await session.writer;
|
||||||
|
if (session.tmpDir)
|
||||||
|
await removeFolders(platform, [session.tmpDir]);
|
||||||
|
stackSessions.delete(stacksId!);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function harOpen(harBackends: Map<string, HarBackend>, params: channels.LocalUtilsHarOpenParams): Promise<channels.LocalUtilsHarOpenResult> {
|
||||||
|
let harBackend: HarBackend;
|
||||||
|
if (params.file.endsWith('.zip')) {
|
||||||
|
const zipFile = new ZipFile(params.file);
|
||||||
|
const entryNames = await zipFile.entries();
|
||||||
|
const harEntryName = entryNames.find(e => e.endsWith('.har'));
|
||||||
|
if (!harEntryName)
|
||||||
|
return { error: 'Specified archive does not have a .har file' };
|
||||||
|
const har = await zipFile.read(harEntryName);
|
||||||
|
const harFile = JSON.parse(har.toString()) as har.HARFile;
|
||||||
|
harBackend = new HarBackend(harFile, null, zipFile);
|
||||||
|
} else {
|
||||||
|
const harFile = JSON.parse(await fs.promises.readFile(params.file, 'utf-8')) as har.HARFile;
|
||||||
|
harBackend = new HarBackend(harFile, path.dirname(params.file), null);
|
||||||
|
}
|
||||||
|
harBackends.set(harBackend.id, harBackend);
|
||||||
|
return { harId: harBackend.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function harLookup(harBackends: Map<string, HarBackend>, params: channels.LocalUtilsHarLookupParams): Promise<channels.LocalUtilsHarLookupResult> {
|
||||||
|
const harBackend = harBackends.get(params.harId);
|
||||||
|
if (!harBackend)
|
||||||
|
return { action: 'error', message: `Internal error: har was not opened` };
|
||||||
|
return await harBackend.lookup(params.url, params.method, params.headers, params.postData, params.isNavigationRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function harClose(harBackends: Map<string, HarBackend>, params: channels.LocalUtilsHarCloseParams): Promise<void> {
|
||||||
|
const harBackend = harBackends.get(params.harId);
|
||||||
|
if (harBackend) {
|
||||||
|
harBackends.delete(harBackend.id);
|
||||||
|
harBackend.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function harUnzip(params: channels.LocalUtilsHarUnzipParams): Promise<void> {
|
||||||
|
const dir = path.dirname(params.zipFile);
|
||||||
|
const zipFile = new ZipFile(params.zipFile);
|
||||||
|
for (const entry of await zipFile.entries()) {
|
||||||
|
const buffer = await zipFile.read(entry);
|
||||||
|
if (entry === 'har.har')
|
||||||
|
await fs.promises.writeFile(params.harFile, buffer);
|
||||||
|
else
|
||||||
|
await fs.promises.writeFile(path.join(dir, entry), buffer);
|
||||||
|
}
|
||||||
|
zipFile.close();
|
||||||
|
await fs.promises.unlink(params.zipFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function tracingStarted(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsTracingStartedParams): Promise<channels.LocalUtilsTracingStartedResult> {
|
||||||
|
let tmpDir = undefined;
|
||||||
|
if (!params.tracesDir)
|
||||||
|
tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-tracing-'));
|
||||||
|
const traceStacksFile = path.join(params.tracesDir || tmpDir!, params.traceName + '.stacks');
|
||||||
|
stackSessions.set(traceStacksFile, { callStacks: [], file: traceStacksFile, writer: Promise.resolve(), tmpDir });
|
||||||
|
return { stacksId: traceStacksFile };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function traceDiscarded(platform: Platform, stackSessions: Map<string, StackSession>, params: channels.LocalUtilsTraceDiscardedParams): Promise<void> {
|
||||||
|
await deleteStackSession(platform, stackSessions, params.stacksId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addStackToTracingNoReply(stackSessions: Map<string, StackSession>, params: channels.LocalUtilsAddStackToTracingNoReplyParams): Promise<void> {
|
||||||
|
for (const session of stackSessions.values()) {
|
||||||
|
session.callStacks.push(params.callData);
|
||||||
|
if (process.env.PW_LIVE_TRACE_STACKS) {
|
||||||
|
session.writer = session.writer.then(() => {
|
||||||
|
const buffer = Buffer.from(JSON.stringify(serializeClientSideCallMetadata(session.callStacks)));
|
||||||
|
return fs.promises.writeFile(session.file, buffer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function urlToWSEndpoint(progress: Progress | undefined, endpointURL: string): Promise<string> {
|
||||||
|
if (endpointURL.startsWith('ws'))
|
||||||
|
return endpointURL;
|
||||||
|
|
||||||
|
progress?.log(`<ws preparing> retrieving websocket url from ${endpointURL}`);
|
||||||
|
const fetchUrl = new URL(endpointURL);
|
||||||
|
if (!fetchUrl.pathname.endsWith('/'))
|
||||||
|
fetchUrl.pathname += '/';
|
||||||
|
fetchUrl.pathname += 'json';
|
||||||
|
const json = await fetchData({
|
||||||
|
url: fetchUrl.toString(),
|
||||||
|
method: 'GET',
|
||||||
|
timeout: progress?.timeUntilDeadline() ?? 30_000,
|
||||||
|
headers: { 'User-Agent': getUserAgent() },
|
||||||
|
}, async (params: HTTPRequestParams, response: http.IncomingMessage) => {
|
||||||
|
return new Error(`Unexpected status ${response.statusCode} when connecting to ${fetchUrl.toString()}.\n` +
|
||||||
|
`This does not look like a Playwright server, try connecting via ws://.`);
|
||||||
|
});
|
||||||
|
progress?.throwIfAborted();
|
||||||
|
|
||||||
|
const wsUrl = new URL(endpointURL);
|
||||||
|
let wsEndpointPath = JSON.parse(json).wsEndpointPath;
|
||||||
|
if (wsEndpointPath.startsWith('/'))
|
||||||
|
wsEndpointPath = wsEndpointPath.substring(1);
|
||||||
|
if (!wsUrl.pathname.endsWith('/'))
|
||||||
|
wsUrl.pathname += '/';
|
||||||
|
wsUrl.pathname += wsEndpointPath;
|
||||||
|
wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
return wsUrl.toString();
|
||||||
|
}
|
||||||
49
packages/playwright-core/src/utils/platform.ts
Normal file
49
packages/playwright-core/src/utils/platform.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as util from 'util';
|
||||||
|
|
||||||
|
export type Platform = {
|
||||||
|
fs: () => typeof fs;
|
||||||
|
path: () => typeof path;
|
||||||
|
inspectCustom: symbol | undefined;
|
||||||
|
ws?: (url: string) => WebSocket;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const emptyPlatform: Platform = {
|
||||||
|
fs: () => {
|
||||||
|
throw new Error('File system is not available');
|
||||||
|
},
|
||||||
|
|
||||||
|
path: () => {
|
||||||
|
throw new Error('Path module is not available');
|
||||||
|
},
|
||||||
|
|
||||||
|
inspectCustom: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const nodePlatform: Platform = {
|
||||||
|
fs: () => fs,
|
||||||
|
path: () => path,
|
||||||
|
inspectCustom: util.inspect.custom,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const webPlatform: Platform = {
|
||||||
|
...emptyPlatform,
|
||||||
|
ws: (url: string) => new WebSocket(url),
|
||||||
|
};
|
||||||
222
packages/playwright-core/types/protocol.d.ts
vendored
222
packages/playwright-core/types/protocol.d.ts
vendored
|
|
@ -952,7 +952,7 @@ Should be updated alongside RequestIdTokenStatus in
|
||||||
third_party/blink/public/mojom/devtools/inspector_issue.mojom to include
|
third_party/blink/public/mojom/devtools/inspector_issue.mojom to include
|
||||||
all cases except for success.
|
all cases except for success.
|
||||||
*/
|
*/
|
||||||
export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"IdpNotPotentiallyTrustworthy"|"DisabledInSettings"|"DisabledInFlags"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenIdpErrorResponse"|"IdTokenCrossSiteIdpErrorResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"|"MissingTransientUserActivation"|"ReplacedByActiveMode"|"InvalidFieldsSpecified"|"RelyingPartyOriginIsOpaque"|"TypeNotMatching";
|
export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"IdpNotPotentiallyTrustworthy"|"DisabledInSettings"|"DisabledInFlags"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenIdpErrorResponse"|"IdTokenCrossSiteIdpErrorResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"|"MissingTransientUserActivation"|"ReplacedByActiveMode"|"InvalidFieldsSpecified"|"RelyingPartyOriginIsOpaque"|"TypeNotMatching"|"UiDismissedNoEmbargo";
|
||||||
export interface FederatedAuthUserInfoRequestIssueDetails {
|
export interface FederatedAuthUserInfoRequestIssueDetails {
|
||||||
federatedAuthUserInfoRequestIssueReason: FederatedAuthUserInfoRequestIssueReason;
|
federatedAuthUserInfoRequestIssueReason: FederatedAuthUserInfoRequestIssueReason;
|
||||||
}
|
}
|
||||||
|
|
@ -983,7 +983,7 @@ features, encourage the use of new ones, and provide general guidance.
|
||||||
}
|
}
|
||||||
export type SelectElementAccessibilityIssueReason = "DisallowedSelectChild"|"DisallowedOptGroupChild"|"NonPhrasingContentOptionChild"|"InteractiveContentOptionChild"|"InteractiveContentLegendChild";
|
export type SelectElementAccessibilityIssueReason = "DisallowedSelectChild"|"DisallowedOptGroupChild"|"NonPhrasingContentOptionChild"|"InteractiveContentOptionChild"|"InteractiveContentLegendChild";
|
||||||
/**
|
/**
|
||||||
* This isue warns about errors in the select element content model.
|
* This issue warns about errors in the select element content model.
|
||||||
*/
|
*/
|
||||||
export interface SelectElementAccessibilityIssueDetails {
|
export interface SelectElementAccessibilityIssueDetails {
|
||||||
nodeId: DOM.BackendNodeId;
|
nodeId: DOM.BackendNodeId;
|
||||||
|
|
@ -1187,6 +1187,19 @@ flag is set.
|
||||||
*/
|
*/
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Uninstalls an unpacked extension (others not supported) from the profile.
|
||||||
|
Available if the client is connected using the --remote-debugging-pipe flag
|
||||||
|
and the --enable-unsafe-extension-debugging.
|
||||||
|
*/
|
||||||
|
export type uninstallParameters = {
|
||||||
|
/**
|
||||||
|
* Extension id.
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
export type uninstallReturnValue = {
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Gets data from extension storage in the given `storageArea`. If `keys` is
|
* Gets data from extension storage in the given `storageArea`. If `keys` is
|
||||||
specified, these are used to filter the result.
|
specified, these are used to filter the result.
|
||||||
|
|
@ -2913,6 +2926,13 @@ incorrect results if the declaration contains a var() for example.
|
||||||
* Identifier of the frame where "via-inspector" stylesheet should be created.
|
* Identifier of the frame where "via-inspector" stylesheet should be created.
|
||||||
*/
|
*/
|
||||||
frameId: Page.FrameId;
|
frameId: Page.FrameId;
|
||||||
|
/**
|
||||||
|
* If true, creates a new stylesheet for every call. If false,
|
||||||
|
returns a stylesheet previously created by a call with force=false
|
||||||
|
for the frame's document if it exists or creates a new stylesheet
|
||||||
|
(default: false).
|
||||||
|
*/
|
||||||
|
force?: boolean;
|
||||||
}
|
}
|
||||||
export type createStyleSheetReturnValue = {
|
export type createStyleSheetReturnValue = {
|
||||||
/**
|
/**
|
||||||
|
|
@ -5974,81 +5994,6 @@ The final text color opacity is computed based on the opacity of all overlapping
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export module Database {
|
|
||||||
/**
|
|
||||||
* Unique identifier of Database object.
|
|
||||||
*/
|
|
||||||
export type DatabaseId = string;
|
|
||||||
/**
|
|
||||||
* Database object.
|
|
||||||
*/
|
|
||||||
export interface Database {
|
|
||||||
/**
|
|
||||||
* Database ID.
|
|
||||||
*/
|
|
||||||
id: DatabaseId;
|
|
||||||
/**
|
|
||||||
* Database domain.
|
|
||||||
*/
|
|
||||||
domain: string;
|
|
||||||
/**
|
|
||||||
* Database name.
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* Database version.
|
|
||||||
*/
|
|
||||||
version: string;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Database error.
|
|
||||||
*/
|
|
||||||
export interface Error {
|
|
||||||
/**
|
|
||||||
* Error message.
|
|
||||||
*/
|
|
||||||
message: string;
|
|
||||||
/**
|
|
||||||
* Error code.
|
|
||||||
*/
|
|
||||||
code: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type addDatabasePayload = {
|
|
||||||
database: Database;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disables database tracking, prevents database events from being sent to the client.
|
|
||||||
*/
|
|
||||||
export type disableParameters = {
|
|
||||||
}
|
|
||||||
export type disableReturnValue = {
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Enables database tracking, database events will now be delivered to the client.
|
|
||||||
*/
|
|
||||||
export type enableParameters = {
|
|
||||||
}
|
|
||||||
export type enableReturnValue = {
|
|
||||||
}
|
|
||||||
export type executeSQLParameters = {
|
|
||||||
databaseId: DatabaseId;
|
|
||||||
query: string;
|
|
||||||
}
|
|
||||||
export type executeSQLReturnValue = {
|
|
||||||
columnNames?: string[];
|
|
||||||
values?: any[];
|
|
||||||
sqlError?: Error;
|
|
||||||
}
|
|
||||||
export type getDatabaseTableNamesParameters = {
|
|
||||||
databaseId: DatabaseId;
|
|
||||||
}
|
|
||||||
export type getDatabaseTableNamesReturnValue = {
|
|
||||||
tableNames: string[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export module DeviceOrientation {
|
export module DeviceOrientation {
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -9126,7 +9071,7 @@ This is a temporary ability and it will be removed in the future.
|
||||||
/**
|
/**
|
||||||
* Types of reasons why a cookie should have been blocked by 3PCD but is exempted for the request.
|
* Types of reasons why a cookie should have been blocked by 3PCD but is exempted for the request.
|
||||||
*/
|
*/
|
||||||
export type CookieExemptionReason = "None"|"UserSetting"|"TPCDMetadata"|"TPCDDeprecationTrial"|"TopLevelTPCDDeprecationTrial"|"TPCDHeuristics"|"EnterprisePolicy"|"StorageAccess"|"TopLevelStorageAccess"|"Scheme";
|
export type CookieExemptionReason = "None"|"UserSetting"|"TPCDMetadata"|"TPCDDeprecationTrial"|"TopLevelTPCDDeprecationTrial"|"TPCDHeuristics"|"EnterprisePolicy"|"StorageAccess"|"TopLevelStorageAccess"|"Scheme"|"SameSiteNoneCookiesInSandbox";
|
||||||
/**
|
/**
|
||||||
* A cookie which was not stored from a response with the corresponding reason.
|
* A cookie which was not stored from a response with the corresponding reason.
|
||||||
*/
|
*/
|
||||||
|
|
@ -11702,7 +11647,7 @@ as an ad.
|
||||||
* All Permissions Policy features. This enum should match the one defined
|
* All Permissions Policy features. This enum should match the one defined
|
||||||
in third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5.
|
in third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5.
|
||||||
*/
|
*/
|
||||||
export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"controlled-frame"|"cross-origin-isolated"|"deferred-fetch"|"deferred-fetch-minimal"|"digital-credentials-get"|"direct-sockets"|"direct-sockets-private"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"fenced-unpartitioned-storage-read"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"media-playback-while-not-visible"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"popins"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-app-installation"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking";
|
export type PermissionsPolicyFeature = "accelerometer"|"all-screens-capture"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-high-entropy-values"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"controlled-frame"|"cross-origin-isolated"|"deferred-fetch"|"deferred-fetch-minimal"|"digital-credentials-get"|"direct-sockets"|"direct-sockets-private"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"fenced-unpartitioned-storage-read"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"media-playback-while-not-visible"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"popins"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-app-installation"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking";
|
||||||
/**
|
/**
|
||||||
* Reason for a permissions policy feature to be disabled.
|
* Reason for a permissions policy feature to be disabled.
|
||||||
*/
|
*/
|
||||||
|
|
@ -12431,6 +12376,33 @@ subtree is actually detached.
|
||||||
frame: Frame;
|
frame: Frame;
|
||||||
}
|
}
|
||||||
export type frameResizedPayload = void;
|
export type frameResizedPayload = void;
|
||||||
|
/**
|
||||||
|
* Fired when a navigation starts. This event is fired for both
|
||||||
|
renderer-initiated and browser-initiated navigations. For renderer-initiated
|
||||||
|
navigations, the event is fired after `frameRequestedNavigation`.
|
||||||
|
Navigation may still be cancelled after the event is issued. Multiple events
|
||||||
|
can be fired for a single navigation, for example, when a same-document
|
||||||
|
navigation becomes a cross-document navigation (such as in the case of a
|
||||||
|
frameset).
|
||||||
|
*/
|
||||||
|
export type frameStartedNavigatingPayload = {
|
||||||
|
/**
|
||||||
|
* ID of the frame that is being navigated.
|
||||||
|
*/
|
||||||
|
frameId: FrameId;
|
||||||
|
/**
|
||||||
|
* The URL the navigation started with. The final URL can be different.
|
||||||
|
*/
|
||||||
|
url: string;
|
||||||
|
/**
|
||||||
|
* Loader identifier. Even though it is present in case of same-document
|
||||||
|
navigation, the previously committed loaderId would not change unless
|
||||||
|
the navigation changes from a same-document to a cross-document
|
||||||
|
navigation.
|
||||||
|
*/
|
||||||
|
loaderId: Network.LoaderId;
|
||||||
|
navigationType: "reload"|"reloadBypassingCache"|"restore"|"restoreWithPost"|"historySameDocument"|"historyDifferentDocument"|"sameDocument"|"differentDocument";
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Fired when a renderer-initiated navigation is requested.
|
* Fired when a renderer-initiated navigation is requested.
|
||||||
Navigation may still be cancelled after the event is issued.
|
Navigation may still be cancelled after the event is issued.
|
||||||
|
|
@ -14281,7 +14253,7 @@ For cached script it is the last time the cache entry was validated.
|
||||||
/**
|
/**
|
||||||
* Enum of possible storage types.
|
* Enum of possible storage types.
|
||||||
*/
|
*/
|
||||||
export type StorageType = "appcache"|"cookies"|"file_systems"|"indexeddb"|"local_storage"|"shader_cache"|"websql"|"service_workers"|"cache_storage"|"interest_groups"|"shared_storage"|"storage_buckets"|"all"|"other";
|
export type StorageType = "cookies"|"file_systems"|"indexeddb"|"local_storage"|"shader_cache"|"websql"|"service_workers"|"cache_storage"|"interest_groups"|"shared_storage"|"storage_buckets"|"all"|"other";
|
||||||
/**
|
/**
|
||||||
* Usage for a storage type.
|
* Usage for a storage type.
|
||||||
*/
|
*/
|
||||||
|
|
@ -15193,6 +15165,28 @@ session. The effective Related Website Sets will not change during a browser ses
|
||||||
export type getRelatedWebsiteSetsReturnValue = {
|
export type getRelatedWebsiteSetsReturnValue = {
|
||||||
sets: RelatedWebsiteSet[];
|
sets: RelatedWebsiteSet[];
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Returns the list of URLs from a page and its embedded resources that match
|
||||||
|
existing grace period URL pattern rules.
|
||||||
|
https://developers.google.com/privacy-sandbox/cookies/temporary-exceptions/grace-period
|
||||||
|
*/
|
||||||
|
export type getAffectedUrlsForThirdPartyCookieMetadataParameters = {
|
||||||
|
/**
|
||||||
|
* The URL of the page currently being visited.
|
||||||
|
*/
|
||||||
|
firstPartyUrl: string;
|
||||||
|
/**
|
||||||
|
* The list of embedded resource URLs from the page.
|
||||||
|
*/
|
||||||
|
thirdPartyUrls: string[];
|
||||||
|
}
|
||||||
|
export type getAffectedUrlsForThirdPartyCookieMetadataReturnValue = {
|
||||||
|
/**
|
||||||
|
* Array of matching URLs. If there is a primary pattern match for the first-
|
||||||
|
party URL, only the first-party URL is returned in the array.
|
||||||
|
*/
|
||||||
|
matchedUrls: string[];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -15485,6 +15479,10 @@ If filter is not specified, the one assumed is
|
||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The state of the target window.
|
||||||
|
*/
|
||||||
|
export type WindowState = "normal"|"minimized"|"maximized"|"fullscreen";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Issued when attached to target because of auto-attach or `attachToTarget` command.
|
* Issued when attached to target because of auto-attach or `attachToTarget` command.
|
||||||
|
|
@ -15677,37 +15675,42 @@ Parts of the URL other than those constituting origin are ignored.
|
||||||
*/
|
*/
|
||||||
url: string;
|
url: string;
|
||||||
/**
|
/**
|
||||||
* Frame left origin in DIP (headless chrome only).
|
* Frame left origin in DIP (requires newWindow to be true or headless shell).
|
||||||
*/
|
*/
|
||||||
left?: number;
|
left?: number;
|
||||||
/**
|
/**
|
||||||
* Frame top origin in DIP (headless chrome only).
|
* Frame top origin in DIP (requires newWindow to be true or headless shell).
|
||||||
*/
|
*/
|
||||||
top?: number;
|
top?: number;
|
||||||
/**
|
/**
|
||||||
* Frame width in DIP (headless chrome only).
|
* Frame width in DIP (requires newWindow to be true or headless shell).
|
||||||
*/
|
*/
|
||||||
width?: number;
|
width?: number;
|
||||||
/**
|
/**
|
||||||
* Frame height in DIP (headless chrome only).
|
* Frame height in DIP (requires newWindow to be true or headless shell).
|
||||||
*/
|
*/
|
||||||
height?: number;
|
height?: number;
|
||||||
|
/**
|
||||||
|
* Frame window state (requires newWindow to be true or headless shell).
|
||||||
|
Default is normal.
|
||||||
|
*/
|
||||||
|
windowState?: WindowState;
|
||||||
/**
|
/**
|
||||||
* The browser context to create the page in.
|
* The browser context to create the page in.
|
||||||
*/
|
*/
|
||||||
browserContextId?: Browser.BrowserContextID;
|
browserContextId?: Browser.BrowserContextID;
|
||||||
/**
|
/**
|
||||||
* Whether BeginFrames for this target will be controlled via DevTools (headless chrome only,
|
* Whether BeginFrames for this target will be controlled via DevTools (headless shell only,
|
||||||
not supported on MacOS yet, false by default).
|
not supported on MacOS yet, false by default).
|
||||||
*/
|
*/
|
||||||
enableBeginFrameControl?: boolean;
|
enableBeginFrameControl?: boolean;
|
||||||
/**
|
/**
|
||||||
* Whether to create a new Window or Tab (chrome-only, false by default).
|
* Whether to create a new Window or Tab (false by default, not supported by headless shell).
|
||||||
*/
|
*/
|
||||||
newWindow?: boolean;
|
newWindow?: boolean;
|
||||||
/**
|
/**
|
||||||
* Whether to create the target in background or foreground (chrome-only,
|
* Whether to create the target in background or foreground (false by default, not supported
|
||||||
false by default).
|
by headless shell).
|
||||||
*/
|
*/
|
||||||
background?: boolean;
|
background?: boolean;
|
||||||
/**
|
/**
|
||||||
|
|
@ -18012,9 +18015,20 @@ variables as its properties.
|
||||||
*/
|
*/
|
||||||
externalURL?: string;
|
externalURL?: string;
|
||||||
}
|
}
|
||||||
|
export interface ResolvedBreakpoint {
|
||||||
|
/**
|
||||||
|
* Breakpoint unique identifier.
|
||||||
|
*/
|
||||||
|
breakpointId: BreakpointId;
|
||||||
|
/**
|
||||||
|
* Actual breakpoint location.
|
||||||
|
*/
|
||||||
|
location: Location;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired when breakpoint is resolved to an actual script and location.
|
* Fired when breakpoint is resolved to an actual script and location.
|
||||||
|
Deprecated in favor of `resolvedBreakpoints` in the `scriptParsed` event.
|
||||||
*/
|
*/
|
||||||
export type breakpointResolvedPayload = {
|
export type breakpointResolvedPayload = {
|
||||||
/**
|
/**
|
||||||
|
|
@ -18225,6 +18239,12 @@ scripts upon enabling debugger.
|
||||||
* The name the embedder supplied for this script.
|
* The name the embedder supplied for this script.
|
||||||
*/
|
*/
|
||||||
embedderName?: string;
|
embedderName?: string;
|
||||||
|
/**
|
||||||
|
* The list of set breakpoints in this script if calls to `setBreakpointByUrl`
|
||||||
|
matches this script's URL or hash. Clients that use this list can ignore the
|
||||||
|
`breakpointResolved` event. They are equivalent.
|
||||||
|
*/
|
||||||
|
resolvedBreakpoints?: ResolvedBreakpoint[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -20150,13 +20170,21 @@ It is the total usage of the corresponding isolate not scoped to a particular Ru
|
||||||
}
|
}
|
||||||
export type getHeapUsageReturnValue = {
|
export type getHeapUsageReturnValue = {
|
||||||
/**
|
/**
|
||||||
* Used heap size in bytes.
|
* Used JavaScript heap size in bytes.
|
||||||
*/
|
*/
|
||||||
usedSize: number;
|
usedSize: number;
|
||||||
/**
|
/**
|
||||||
* Allocated heap size in bytes.
|
* Allocated JavaScript heap size in bytes.
|
||||||
*/
|
*/
|
||||||
totalSize: number;
|
totalSize: number;
|
||||||
|
/**
|
||||||
|
* Used size in bytes in the embedder's garbage-collected heap.
|
||||||
|
*/
|
||||||
|
embedderHeapUsedSize: number;
|
||||||
|
/**
|
||||||
|
* Size in bytes of backing storage for array buffers and external strings.
|
||||||
|
*/
|
||||||
|
backingStorageSize: number;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Returns properties of a given object. Object group of the result is inherited from the target
|
* Returns properties of a given object. Object group of the result is inherited from the target
|
||||||
|
|
@ -20472,7 +20500,6 @@ Error was thrown.
|
||||||
"DOMStorage.domStorageItemRemoved": DOMStorage.domStorageItemRemovedPayload;
|
"DOMStorage.domStorageItemRemoved": DOMStorage.domStorageItemRemovedPayload;
|
||||||
"DOMStorage.domStorageItemUpdated": DOMStorage.domStorageItemUpdatedPayload;
|
"DOMStorage.domStorageItemUpdated": DOMStorage.domStorageItemUpdatedPayload;
|
||||||
"DOMStorage.domStorageItemsCleared": DOMStorage.domStorageItemsClearedPayload;
|
"DOMStorage.domStorageItemsCleared": DOMStorage.domStorageItemsClearedPayload;
|
||||||
"Database.addDatabase": Database.addDatabasePayload;
|
|
||||||
"Emulation.virtualTimeBudgetExpired": Emulation.virtualTimeBudgetExpiredPayload;
|
"Emulation.virtualTimeBudgetExpired": Emulation.virtualTimeBudgetExpiredPayload;
|
||||||
"Input.dragIntercepted": Input.dragInterceptedPayload;
|
"Input.dragIntercepted": Input.dragInterceptedPayload;
|
||||||
"Inspector.detached": Inspector.detachedPayload;
|
"Inspector.detached": Inspector.detachedPayload;
|
||||||
|
|
@ -20526,6 +20553,7 @@ Error was thrown.
|
||||||
"Page.frameNavigated": Page.frameNavigatedPayload;
|
"Page.frameNavigated": Page.frameNavigatedPayload;
|
||||||
"Page.documentOpened": Page.documentOpenedPayload;
|
"Page.documentOpened": Page.documentOpenedPayload;
|
||||||
"Page.frameResized": Page.frameResizedPayload;
|
"Page.frameResized": Page.frameResizedPayload;
|
||||||
|
"Page.frameStartedNavigating": Page.frameStartedNavigatingPayload;
|
||||||
"Page.frameRequestedNavigation": Page.frameRequestedNavigationPayload;
|
"Page.frameRequestedNavigation": Page.frameRequestedNavigationPayload;
|
||||||
"Page.frameScheduledNavigation": Page.frameScheduledNavigationPayload;
|
"Page.frameScheduledNavigation": Page.frameScheduledNavigationPayload;
|
||||||
"Page.frameStartedLoading": Page.frameStartedLoadingPayload;
|
"Page.frameStartedLoading": Page.frameStartedLoadingPayload;
|
||||||
|
|
@ -20656,6 +20684,7 @@ Error was thrown.
|
||||||
"Audits.checkContrast": Audits.checkContrastParameters;
|
"Audits.checkContrast": Audits.checkContrastParameters;
|
||||||
"Audits.checkFormsIssues": Audits.checkFormsIssuesParameters;
|
"Audits.checkFormsIssues": Audits.checkFormsIssuesParameters;
|
||||||
"Extensions.loadUnpacked": Extensions.loadUnpackedParameters;
|
"Extensions.loadUnpacked": Extensions.loadUnpackedParameters;
|
||||||
|
"Extensions.uninstall": Extensions.uninstallParameters;
|
||||||
"Extensions.getStorageItems": Extensions.getStorageItemsParameters;
|
"Extensions.getStorageItems": Extensions.getStorageItemsParameters;
|
||||||
"Extensions.removeStorageItems": Extensions.removeStorageItemsParameters;
|
"Extensions.removeStorageItems": Extensions.removeStorageItemsParameters;
|
||||||
"Extensions.clearStorageItems": Extensions.clearStorageItemsParameters;
|
"Extensions.clearStorageItems": Extensions.clearStorageItemsParameters;
|
||||||
|
|
@ -20808,10 +20837,6 @@ Error was thrown.
|
||||||
"DOMStorage.getDOMStorageItems": DOMStorage.getDOMStorageItemsParameters;
|
"DOMStorage.getDOMStorageItems": DOMStorage.getDOMStorageItemsParameters;
|
||||||
"DOMStorage.removeDOMStorageItem": DOMStorage.removeDOMStorageItemParameters;
|
"DOMStorage.removeDOMStorageItem": DOMStorage.removeDOMStorageItemParameters;
|
||||||
"DOMStorage.setDOMStorageItem": DOMStorage.setDOMStorageItemParameters;
|
"DOMStorage.setDOMStorageItem": DOMStorage.setDOMStorageItemParameters;
|
||||||
"Database.disable": Database.disableParameters;
|
|
||||||
"Database.enable": Database.enableParameters;
|
|
||||||
"Database.executeSQL": Database.executeSQLParameters;
|
|
||||||
"Database.getDatabaseTableNames": Database.getDatabaseTableNamesParameters;
|
|
||||||
"DeviceOrientation.clearDeviceOrientationOverride": DeviceOrientation.clearDeviceOrientationOverrideParameters;
|
"DeviceOrientation.clearDeviceOrientationOverride": DeviceOrientation.clearDeviceOrientationOverrideParameters;
|
||||||
"DeviceOrientation.setDeviceOrientationOverride": DeviceOrientation.setDeviceOrientationOverrideParameters;
|
"DeviceOrientation.setDeviceOrientationOverride": DeviceOrientation.setDeviceOrientationOverrideParameters;
|
||||||
"Emulation.canEmulate": Emulation.canEmulateParameters;
|
"Emulation.canEmulate": Emulation.canEmulateParameters;
|
||||||
|
|
@ -21088,6 +21113,7 @@ Error was thrown.
|
||||||
"Storage.setAttributionReportingTracking": Storage.setAttributionReportingTrackingParameters;
|
"Storage.setAttributionReportingTracking": Storage.setAttributionReportingTrackingParameters;
|
||||||
"Storage.sendPendingAttributionReports": Storage.sendPendingAttributionReportsParameters;
|
"Storage.sendPendingAttributionReports": Storage.sendPendingAttributionReportsParameters;
|
||||||
"Storage.getRelatedWebsiteSets": Storage.getRelatedWebsiteSetsParameters;
|
"Storage.getRelatedWebsiteSets": Storage.getRelatedWebsiteSetsParameters;
|
||||||
|
"Storage.getAffectedUrlsForThirdPartyCookieMetadata": Storage.getAffectedUrlsForThirdPartyCookieMetadataParameters;
|
||||||
"SystemInfo.getInfo": SystemInfo.getInfoParameters;
|
"SystemInfo.getInfo": SystemInfo.getInfoParameters;
|
||||||
"SystemInfo.getFeatureState": SystemInfo.getFeatureStateParameters;
|
"SystemInfo.getFeatureState": SystemInfo.getFeatureStateParameters;
|
||||||
"SystemInfo.getProcessInfo": SystemInfo.getProcessInfoParameters;
|
"SystemInfo.getProcessInfo": SystemInfo.getProcessInfoParameters;
|
||||||
|
|
@ -21273,6 +21299,7 @@ Error was thrown.
|
||||||
"Audits.checkContrast": Audits.checkContrastReturnValue;
|
"Audits.checkContrast": Audits.checkContrastReturnValue;
|
||||||
"Audits.checkFormsIssues": Audits.checkFormsIssuesReturnValue;
|
"Audits.checkFormsIssues": Audits.checkFormsIssuesReturnValue;
|
||||||
"Extensions.loadUnpacked": Extensions.loadUnpackedReturnValue;
|
"Extensions.loadUnpacked": Extensions.loadUnpackedReturnValue;
|
||||||
|
"Extensions.uninstall": Extensions.uninstallReturnValue;
|
||||||
"Extensions.getStorageItems": Extensions.getStorageItemsReturnValue;
|
"Extensions.getStorageItems": Extensions.getStorageItemsReturnValue;
|
||||||
"Extensions.removeStorageItems": Extensions.removeStorageItemsReturnValue;
|
"Extensions.removeStorageItems": Extensions.removeStorageItemsReturnValue;
|
||||||
"Extensions.clearStorageItems": Extensions.clearStorageItemsReturnValue;
|
"Extensions.clearStorageItems": Extensions.clearStorageItemsReturnValue;
|
||||||
|
|
@ -21425,10 +21452,6 @@ Error was thrown.
|
||||||
"DOMStorage.getDOMStorageItems": DOMStorage.getDOMStorageItemsReturnValue;
|
"DOMStorage.getDOMStorageItems": DOMStorage.getDOMStorageItemsReturnValue;
|
||||||
"DOMStorage.removeDOMStorageItem": DOMStorage.removeDOMStorageItemReturnValue;
|
"DOMStorage.removeDOMStorageItem": DOMStorage.removeDOMStorageItemReturnValue;
|
||||||
"DOMStorage.setDOMStorageItem": DOMStorage.setDOMStorageItemReturnValue;
|
"DOMStorage.setDOMStorageItem": DOMStorage.setDOMStorageItemReturnValue;
|
||||||
"Database.disable": Database.disableReturnValue;
|
|
||||||
"Database.enable": Database.enableReturnValue;
|
|
||||||
"Database.executeSQL": Database.executeSQLReturnValue;
|
|
||||||
"Database.getDatabaseTableNames": Database.getDatabaseTableNamesReturnValue;
|
|
||||||
"DeviceOrientation.clearDeviceOrientationOverride": DeviceOrientation.clearDeviceOrientationOverrideReturnValue;
|
"DeviceOrientation.clearDeviceOrientationOverride": DeviceOrientation.clearDeviceOrientationOverrideReturnValue;
|
||||||
"DeviceOrientation.setDeviceOrientationOverride": DeviceOrientation.setDeviceOrientationOverrideReturnValue;
|
"DeviceOrientation.setDeviceOrientationOverride": DeviceOrientation.setDeviceOrientationOverrideReturnValue;
|
||||||
"Emulation.canEmulate": Emulation.canEmulateReturnValue;
|
"Emulation.canEmulate": Emulation.canEmulateReturnValue;
|
||||||
|
|
@ -21705,6 +21728,7 @@ Error was thrown.
|
||||||
"Storage.setAttributionReportingTracking": Storage.setAttributionReportingTrackingReturnValue;
|
"Storage.setAttributionReportingTracking": Storage.setAttributionReportingTrackingReturnValue;
|
||||||
"Storage.sendPendingAttributionReports": Storage.sendPendingAttributionReportsReturnValue;
|
"Storage.sendPendingAttributionReports": Storage.sendPendingAttributionReportsReturnValue;
|
||||||
"Storage.getRelatedWebsiteSets": Storage.getRelatedWebsiteSetsReturnValue;
|
"Storage.getRelatedWebsiteSets": Storage.getRelatedWebsiteSetsReturnValue;
|
||||||
|
"Storage.getAffectedUrlsForThirdPartyCookieMetadata": Storage.getAffectedUrlsForThirdPartyCookieMetadataReturnValue;
|
||||||
"SystemInfo.getInfo": SystemInfo.getInfoReturnValue;
|
"SystemInfo.getInfo": SystemInfo.getInfoReturnValue;
|
||||||
"SystemInfo.getFeatureState": SystemInfo.getFeatureStateReturnValue;
|
"SystemInfo.getFeatureState": SystemInfo.getFeatureStateReturnValue;
|
||||||
"SystemInfo.getProcessInfo": SystemInfo.getProcessInfoReturnValue;
|
"SystemInfo.getProcessInfo": SystemInfo.getProcessInfoReturnValue;
|
||||||
|
|
|
||||||
10
packages/playwright-core/types/types.d.ts
vendored
10
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -9351,7 +9351,7 @@ export interface BrowserContext {
|
||||||
*/
|
*/
|
||||||
keyEncoded?: Object;
|
keyEncoded?: Object;
|
||||||
|
|
||||||
value: Object;
|
value?: Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
* if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
||||||
|
|
@ -10170,7 +10170,7 @@ export interface Browser {
|
||||||
*/
|
*/
|
||||||
keyEncoded?: Object;
|
keyEncoded?: Object;
|
||||||
|
|
||||||
value: Object;
|
value?: Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
* if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
||||||
|
|
@ -17758,7 +17758,7 @@ export interface APIRequest {
|
||||||
*/
|
*/
|
||||||
keyEncoded?: Object;
|
keyEncoded?: Object;
|
||||||
|
|
||||||
value: Object;
|
value?: Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
* if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
||||||
|
|
@ -18616,7 +18616,7 @@ export interface APIRequestContext {
|
||||||
*/
|
*/
|
||||||
keyEncoded?: Object;
|
keyEncoded?: Object;
|
||||||
|
|
||||||
value: Object;
|
value?: Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
* if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
||||||
|
|
@ -22512,7 +22512,7 @@ export interface BrowserContextOptions {
|
||||||
*/
|
*/
|
||||||
keyEncoded?: Object;
|
keyEncoded?: Object;
|
||||||
|
|
||||||
value: Object;
|
value?: Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
* if `value` is not JSON-serializable, this contains an encoded version that preserves types.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { gracefullyProcessExitDoNotHang, isRegExp } from 'playwright-core/lib/utils';
|
import { gracefullyProcessExitDoNotHang } from 'playwright-core/lib/server';
|
||||||
|
import { isRegExp } from 'playwright-core/lib/utils';
|
||||||
|
|
||||||
import { requireOrImport, setSingleTSConfig, setTransformConfig } from '../transform/transform';
|
import { requireOrImport, setSingleTSConfig, setTransformConfig } from '../transform/transform';
|
||||||
import { errorWithFile, fileIsModule } from '../util';
|
import { errorWithFile, fileIsModule } from '../util';
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@
|
||||||
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { calculateSha1, toPosixPath } from 'playwright-core/lib/utils';
|
import { toPosixPath } from 'playwright-core/lib/server';
|
||||||
|
import { calculateSha1 } from 'playwright-core/lib/utils';
|
||||||
|
|
||||||
import { createFileMatcher } from '../util';
|
import { createFileMatcher } from '../util';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -471,7 +471,7 @@ function normalizeScreenshotMode(screenshot: ScreenshotOption): ScreenshotMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachConnectedHeaderIfNeeded(testInfo: TestInfo, browser: Browser | null) {
|
function attachConnectedHeaderIfNeeded(testInfo: TestInfo, browser: Browser | null) {
|
||||||
const connectHeaders: { name: string, value: string }[] | undefined = (browser as any)?._connectHeaders;
|
const connectHeaders: { name: string, value: string }[] | undefined = (browser as any)?._connection.headers;
|
||||||
if (!connectHeaders)
|
if (!connectHeaders)
|
||||||
return;
|
return;
|
||||||
for (const header of connectHeaders) {
|
for (const header of connectHeaders) {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { escapeTemplateString, isString, sanitizeForFilePath } from 'playwright-core/lib/utils';
|
import { sanitizeForFilePath } from 'playwright-core/lib/server';
|
||||||
|
import { escapeTemplateString, isString } from 'playwright-core/lib/utils';
|
||||||
|
|
||||||
import { kNoElementsFoundError, matcherHint } from './matcherHint';
|
import { kNoElementsFoundError, matcherHint } from './matcherHint';
|
||||||
import { EXPECTED_COLOR } from '../common/expectBundle';
|
import { EXPECTED_COLOR } from '../common/expectBundle';
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { compareBuffersOrStrings, getComparator, isString, sanitizeForFilePath } from 'playwright-core/lib/utils';
|
import { sanitizeForFilePath } from 'playwright-core/lib/server';
|
||||||
|
import { compareBuffersOrStrings, getComparator, isString } from 'playwright-core/lib/utils';
|
||||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||||
import { mime } from 'playwright-core/lib/utilsBundle';
|
import { mime } from 'playwright-core/lib/utilsBundle';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@
|
||||||
import * as net from 'net';
|
import * as net from 'net';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { isURLAvailable, launchProcess, monotonicTime, raceAgainstDeadline } from 'playwright-core/lib/utils';
|
import { launchProcess } from 'playwright-core/lib/server';
|
||||||
|
import { isURLAvailable, monotonicTime, raceAgainstDeadline } from 'playwright-core/lib/utils';
|
||||||
import { colors, debug } from 'playwright-core/lib/utilsBundle';
|
import { colors, debug } from 'playwright-core/lib/utilsBundle';
|
||||||
|
|
||||||
import type { TestRunnerPlugin } from '.';
|
import type { TestRunnerPlugin } from '.';
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { program } from 'playwright-core/lib/cli/program';
|
import { program } from 'playwright-core/lib/cli/program';
|
||||||
import { gracefullyProcessExitDoNotHang, startProfiling, stopProfiling } from 'playwright-core/lib/utils';
|
import { gracefullyProcessExitDoNotHang } from 'playwright-core/lib/server';
|
||||||
|
import { startProfiling, stopProfiling } from 'playwright-core/lib/utils';
|
||||||
|
|
||||||
import { builtInReporters, defaultReporter, defaultTimeout } from './common/config';
|
import { builtInReporters, defaultReporter, defaultTimeout } from './common/config';
|
||||||
import { loadConfigFromFileRestartIfNeeded, loadEmptyConfigForMergeReports, resolveConfigLocation } from './common/configLoader';
|
import { loadConfigFromFileRestartIfNeeded, loadEmptyConfigForMergeReports, resolveConfigLocation } from './common/configLoader';
|
||||||
|
|
@ -28,6 +29,7 @@ export { program } from 'playwright-core/lib/cli/program';
|
||||||
import { prepareErrorStack } from './reporters/base';
|
import { prepareErrorStack } from './reporters/base';
|
||||||
import { showHTMLReport } from './reporters/html';
|
import { showHTMLReport } from './reporters/html';
|
||||||
import { createMergedReport } from './reporters/merge';
|
import { createMergedReport } from './reporters/merge';
|
||||||
|
import { filterProjects } from './runner/projectUtils';
|
||||||
import { Runner } from './runner/runner';
|
import { Runner } from './runner/runner';
|
||||||
import * as testServer from './runner/testServer';
|
import * as testServer from './runner/testServer';
|
||||||
import { runWatchModeLoop } from './runner/watchMode';
|
import { runWatchModeLoop } from './runner/watchMode';
|
||||||
|
|
@ -160,6 +162,23 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
|
||||||
await startProfiling();
|
await startProfiling();
|
||||||
const cliOverrides = overridesFromOptions(opts);
|
const cliOverrides = overridesFromOptions(opts);
|
||||||
|
|
||||||
|
const config = await loadConfigFromFileRestartIfNeeded(opts.config, cliOverrides, opts.deps === false);
|
||||||
|
if (!config)
|
||||||
|
return;
|
||||||
|
|
||||||
|
config.cliArgs = args;
|
||||||
|
config.cliGrep = opts.grep as string | undefined;
|
||||||
|
config.cliOnlyChanged = opts.onlyChanged === true ? 'HEAD' : opts.onlyChanged;
|
||||||
|
config.cliGrepInvert = opts.grepInvert as string | undefined;
|
||||||
|
config.cliListOnly = !!opts.list;
|
||||||
|
config.cliProjectFilter = opts.project || undefined;
|
||||||
|
config.cliPassWithNoTests = !!opts.passWithNoTests;
|
||||||
|
config.cliFailOnFlakyTests = !!opts.failOnFlakyTests;
|
||||||
|
config.cliLastFailed = !!opts.lastFailed;
|
||||||
|
|
||||||
|
// Evaluate project filters against config before starting execution. This enables a consistent error message across run modes
|
||||||
|
filterProjects(config.projects, config.cliProjectFilter);
|
||||||
|
|
||||||
if (opts.ui || opts.uiHost || opts.uiPort) {
|
if (opts.ui || opts.uiHost || opts.uiPort) {
|
||||||
if (opts.onlyChanged)
|
if (opts.onlyChanged)
|
||||||
throw new Error(`--only-changed is not supported in UI mode. If you'd like that to change, see https://github.com/microsoft/playwright/issues/15075 for more details.`);
|
throw new Error(`--only-changed is not supported in UI mode. If you'd like that to change, see https://github.com/microsoft/playwright/issues/15075 for more details.`);
|
||||||
|
|
@ -201,20 +220,6 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await loadConfigFromFileRestartIfNeeded(opts.config, cliOverrides, opts.deps === false);
|
|
||||||
if (!config)
|
|
||||||
return;
|
|
||||||
|
|
||||||
config.cliArgs = args;
|
|
||||||
config.cliGrep = opts.grep as string | undefined;
|
|
||||||
config.cliOnlyChanged = opts.onlyChanged === true ? 'HEAD' : opts.onlyChanged;
|
|
||||||
config.cliGrepInvert = opts.grepInvert as string | undefined;
|
|
||||||
config.cliListOnly = !!opts.list;
|
|
||||||
config.cliProjectFilter = opts.project || undefined;
|
|
||||||
config.cliPassWithNoTests = !!opts.passWithNoTests;
|
|
||||||
config.cliFailOnFlakyTests = !!opts.failOnFlakyTests;
|
|
||||||
config.cliLastFailed = !!opts.lastFailed;
|
|
||||||
|
|
||||||
const runner = new Runner(config);
|
const runner = new Runner(config);
|
||||||
const status = await runner.runAllTests();
|
const status = await runner.runAllTests();
|
||||||
await stopProfiling('runner');
|
await stopProfiling('runner');
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
|
|
||||||
import { ManualPromise, calculateSha1, createGuid, getUserAgent, removeFolders, sanitizeForFilePath } from 'playwright-core/lib/utils';
|
import { removeFolders, sanitizeForFilePath } from 'playwright-core/lib/server';
|
||||||
|
import { ManualPromise, calculateSha1, createGuid, getUserAgent } from 'playwright-core/lib/utils';
|
||||||
import { mime } from 'playwright-core/lib/utilsBundle';
|
import { mime } from 'playwright-core/lib/utilsBundle';
|
||||||
import { yazl } from 'playwright-core/lib/zipBundle';
|
import { yazl } from 'playwright-core/lib/zipBundle';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { Transform } from 'stream';
|
import { Transform } from 'stream';
|
||||||
|
|
||||||
import { MultiMap, getPackageManagerExecCommand } from 'playwright-core/lib/utils';
|
import { copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath, toPosixPath } from 'playwright-core/lib/server';
|
||||||
import { HttpServer, assert, calculateSha1, copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath, toPosixPath } from 'playwright-core/lib/utils';
|
import { HttpServer, MultiMap, assert, calculateSha1, getPackageManagerExecCommand } from 'playwright-core/lib/utils';
|
||||||
import { colors, open } from 'playwright-core/lib/utilsBundle';
|
import { colors, open } from 'playwright-core/lib/utilsBundle';
|
||||||
import { mime } from 'playwright-core/lib/utilsBundle';
|
import { mime } from 'playwright-core/lib/utilsBundle';
|
||||||
import { yazl } from 'playwright-core/lib/zipBundle';
|
import { yazl } from 'playwright-core/lib/zipBundle';
|
||||||
|
|
@ -449,6 +449,17 @@ class HtmlBuilder {
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (a.name === 'pageSnapshot') {
|
||||||
|
try {
|
||||||
|
const body = fs.readFileSync(a.path!, { encoding: 'utf-8' });
|
||||||
|
return {
|
||||||
|
name: 'pageSnapshot',
|
||||||
|
contentType: a.contentType,
|
||||||
|
body,
|
||||||
|
};
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
if (a.path) {
|
if (a.path) {
|
||||||
let fileName = a.path;
|
let fileName = a.path;
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { MultiMap, toPosixPath } from 'playwright-core/lib/utils';
|
import { toPosixPath } from 'playwright-core/lib/server';
|
||||||
|
import { MultiMap } from 'playwright-core/lib/utils';
|
||||||
|
|
||||||
import { formatError, nonTerminalScreen, prepareErrorStack, resolveOutputFile } from './base';
|
import { formatError, nonTerminalScreen, prepareErrorStack, resolveOutputFile } from './base';
|
||||||
import { getProjectId } from '../common/config';
|
import { getProjectId } from '../common/config';
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
|
|
||||||
import { monotonicTime, removeFolders } from 'playwright-core/lib/utils';
|
import { removeFolders } from 'playwright-core/lib/server';
|
||||||
|
import { monotonicTime } from 'playwright-core/lib/utils';
|
||||||
import { debug } from 'playwright-core/lib/utilsBundle';
|
import { debug } from 'playwright-core/lib/utilsBundle';
|
||||||
|
|
||||||
import { Dispatcher } from './dispatcher';
|
import { Dispatcher } from './dispatcher';
|
||||||
|
|
@ -26,12 +27,12 @@ import { FailureTracker } from './failureTracker';
|
||||||
import { collectProjectsAndTestFiles, createRootSuite, loadFileSuites, loadGlobalHook } from './loadUtils';
|
import { collectProjectsAndTestFiles, createRootSuite, loadFileSuites, loadGlobalHook } from './loadUtils';
|
||||||
import { buildDependentProjects, buildTeardownToSetupsMap, filterProjects } from './projectUtils';
|
import { buildDependentProjects, buildTeardownToSetupsMap, filterProjects } from './projectUtils';
|
||||||
import { applySuggestedRebaselines, clearSuggestedRebaselines } from './rebase';
|
import { applySuggestedRebaselines, clearSuggestedRebaselines } from './rebase';
|
||||||
import { Suite } from '../common/test';
|
|
||||||
import { createTestGroups } from '../runner/testGroups';
|
|
||||||
import { removeDirAndLogToConsole } from '../util';
|
|
||||||
import { TaskRunner } from './taskRunner';
|
import { TaskRunner } from './taskRunner';
|
||||||
import { detectChangedTestFiles } from './vcs';
|
import { detectChangedTestFiles } from './vcs';
|
||||||
|
import { Suite } from '../common/test';
|
||||||
|
import { createTestGroups } from '../runner/testGroups';
|
||||||
import { cacheDir } from '../transform/compilationCache';
|
import { cacheDir } from '../transform/compilationCache';
|
||||||
|
import { removeDirAndLogToConsole } from '../util';
|
||||||
|
|
||||||
import type { TestGroup } from '../runner/testGroups';
|
import type { TestGroup } from '../runner/testGroups';
|
||||||
import type { Matcher } from '../util';
|
import type { Matcher } from '../util';
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { installRootRedirect, openTraceInBrowser, openTraceViewerApp, registry, startTraceViewerServer } from 'playwright-core/lib/server';
|
import { gracefullyProcessExitDoNotHang, installRootRedirect, openTraceInBrowser, openTraceViewerApp, registry, startTraceViewerServer } from 'playwright-core/lib/server';
|
||||||
import { ManualPromise, gracefullyProcessExitDoNotHang, isUnderTest } from 'playwright-core/lib/utils';
|
import { ManualPromise, isUnderTest } from 'playwright-core/lib/utils';
|
||||||
import { open } from 'playwright-core/lib/utilsBundle';
|
import { open } from 'playwright-core/lib/utilsBundle';
|
||||||
|
|
||||||
import { createErrorCollectingReporter, createReporterForTestServer, createReporters } from './reporters';
|
import { createErrorCollectingReporter, createReporterForTestServer, createReporters } from './reporters';
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import { removeFolders } from 'playwright-core/lib/utils';
|
import { removeFolders } from 'playwright-core/lib/server';
|
||||||
|
|
||||||
import { ProcessHost } from './processHost';
|
import { ProcessHost } from './processHost';
|
||||||
import { stdioChunkToParams } from '../common/ipc';
|
import { stdioChunkToParams } from '../common/ipc';
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import * as path from 'path';
|
||||||
import * as url from 'url';
|
import * as url from 'url';
|
||||||
import util from 'util';
|
import util from 'util';
|
||||||
|
|
||||||
import { formatCallLog } from 'playwright-core/lib/utils';
|
import { sanitizeForFilePath } from 'playwright-core/lib/server';
|
||||||
import { calculateSha1, isRegExp, isString, sanitizeForFilePath, stringifyStackFrames } from 'playwright-core/lib/utils';
|
import { calculateSha1, formatCallLog, isRegExp, isString, stringifyStackFrames } from 'playwright-core/lib/utils';
|
||||||
import { debug, mime, minimatch, parseStackTraceLine } from 'playwright-core/lib/utilsBundle';
|
import { debug, mime, minimatch, parseStackTraceLine } from 'playwright-core/lib/utilsBundle';
|
||||||
|
|
||||||
import type { Location } from './../types/testReporter';
|
import type { Location } from './../types/testReporter';
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue