Compare commits

...

31 commits

Author SHA1 Message Date
Yury Semikhatsky ba2035b031
chore: mark 1.23.4 (#15731) 2022-07-15 17:23:37 -07:00
Yury Semikhatsky 47f428a00b
fix(esm loader): support Node 18.6 (#15730) 2022-07-15 16:39:02 -07:00
Andrey Lushnikov 6365c0fe6c
chore: mark v1.23.3 (#15608) 2022-07-13 04:49:53 -07:00
Pavel Feldman 0dade6ef0e cherry-pick(#15588): chore: remove joining emitter 2022-07-12 13:05:27 -07:00
Andrey Lushnikov 56e04df8aa
chore: mark v1.23.2 (#15463) 2022-07-07 11:27:53 -07:00
Pavel Feldman a2acf769eb cherry-pick(#15430): chore: respect event name in removeAllListeners 2022-07-06 15:05:10 -07:00
Pavel Feldman 741e338fb1 cherry-pick(#15422): fix(ct): tolerate compiled react components 2022-07-06 14:02:09 -07:00
Pavel Feldman 19790a0e70 cherry-pick(#15309): fix(config): fall back to context options 2022-07-01 10:22:53 -07:00
Pavel Feldman 2b64bca12b cherry-pick(#15293): fix(config): fall back to launch options 2022-07-01 09:28:12 -07:00
Max Schmitt 354f6ea337
cherry-pick(e9c42a16): docs(release-notes): no jammy Docker image for language bindings (#15305) 2022-07-01 15:03:31 +02:00
Max Schmitt e9c42a16d8
cherry-pick(#15295): docs(release-notes): add 1.23 release notes for .NET (#15302)
SHA: 1e28870af7
2022-07-01 14:50:39 +02:00
Ross Wollman a126a2f220 cherry-pick(#15289): chore: port 1.23 Python release notes 2022-06-30 16:44:37 -07:00
Andrey Lushnikov a19117aa6b
chore: mark v1.23.1 (#15248) 2022-06-30 09:53:22 -07:00
Max Schmitt e9200c5f9f
cherry-pick(#15237): chore: roll source-map-support to 0.5.21 (#15241)
SHA: 28f382bea6
Co-authored-by: Max Schmitt <max@schmitt.mx>
2022-06-29 21:29:53 +02:00
Max Schmitt 389aa59470
cherry-pick(#15224): fix: do not throw on removeListener without listener (#15227)
SHA: b3c31f5b13
2022-06-29 14:44:48 +02:00
Pavel Feldman 140f8e45f9 cherry-pick(#15197): chore: allow updating har while routing 2022-06-28 15:57:35 -07:00
Ross Wollman a8cf285df6
cherry-pick(#15192): docs(java): fix pom.xml (#15199) 2022-06-28 13:49:54 -07:00
Ross Wollman 25cd4c9a30 cherry-pick(#15162): docs: fix routing and har examples 2022-06-27 20:35:37 -07:00
Andrey Lushnikov 7db695ffb5
cherry-pick(#15158) docs: add Java release notes (#15160) 2022-06-27 15:34:37 -07:00
Pavel Feldman 5c6335edda cherry-pick(#15152): chore: undeprecate sync headers() 2022-06-27 10:38:20 -07:00
Andrey Lushnikov 29ec22b9c9
cherry-pick(#15111): feat(chromium): roll to r1012 (#15144)
SHA: fab12c70f7

Co-authored-by: Playwright Service <89237858+playwrightmachine@users.noreply.github.com>
2022-06-27 04:52:27 -07:00
Pavel Feldman 8f292e09bf cherry-pick(#15112): fix(route): match against updated url while chaining 2022-06-24 10:52:05 -07:00
Yury Semikhatsky 270675a89f
cherry-pick(#15097): docs: recordHar content and mode options for lan… (#15098) 2022-06-23 18:58:17 -07:00
Pavel Feldman 7b78e71433 cherry-pick(#15093): chore: fix api.json 2022-06-23 16:23:25 -07:00
Andrey Lushnikov c74c9a3690
chore: mark v1.23.0 (#15091) 2022-06-23 16:21:17 -07:00
Andrey Lushnikov c175d6fa8e
cherry-pick(#15090): docs: add release notes for 1.23 javascript (#15092) 2022-06-23 16:18:02 -07:00
Pavel Feldman 7f74063549 cherry-pick(#15053): feat(har): introduce the slim mode 2022-06-22 14:58:01 -07:00
Dmitry Gozman d3e80664e8
cherry-pick(#15046): fix(har): remove types/har.d.ts, update har.ts per spec (#15054)
Drive-by: typo fix in `notFound` option name.
2022-06-22 14:38:34 -07:00
Dmitry Gozman c840a28946
cherry-pick(#15024): feat(har): re-add routeFromHAR (#15047)
Co-authored-by: Pavel Feldman <pavel.feldman@gmail.com>
2022-06-22 12:33:19 -07:00
Dmitry Gozman 82769c23de
cherry-pick(#15000): fix(har): internal redirect in renderer-initiated navigations (#15045) 2022-06-22 11:22:52 -07:00
Andrey Lushnikov 0b30dd2537
cherry-pick(#15012): feat(webkit): roll to r1668 (#15040)
This patch cherry-picks two PRs:
* feat(webkit): roll to r1667 (#14960)
* feat(webkit): roll to r1668 (#15012)
2022-06-22 10:15:29 -07:00
94 changed files with 1987 additions and 947 deletions

View file

@ -1,6 +1,6 @@
# 🎭 Playwright # 🎭 Playwright
[![npm version](https://img.shields.io/npm/v/playwright.svg?style=flat)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-103.0.5060.53-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-100.0.2-blue.svg?logo=mozilla-firefox)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-15.4-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![npm version](https://img.shields.io/npm/v/playwright.svg?style=flat)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-104.0.5112.20-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-100.0.2-blue.svg?logo=mozilla-firefox)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-15.4-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop -->
## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)
@ -8,7 +8,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr
| | Linux | macOS | Windows | | | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: | | :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->103.0.5060.53<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Chromium <!-- GEN:chromium-version -->104.0.5112.20<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->100.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox <!-- GEN:firefox-version -->100.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |

View file

@ -1025,6 +1025,35 @@ handler function to route the request.
How often a route should be used. By default it will be used every time. How often a route should be used. By default it will be used every time.
## async method: BrowserContext.routeFromHAR
If specified the network requests that are made in the context will be served from the HAR file. Read more about [Replaying from HAR](../network.md#replaying-from-har).
Playwright will not serve requests intercepted by Service Worker from the HAR file. See [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend disabling Service Workers when using request interception by setting [`option: Browser.newContext.serviceWorkers`] to `'block'`.
### param: BrowserContext.routeFromHAR.har
- `har` <[path]>
Path to a [HAR](http://www.softwareishard.com/blog/har-12-spec) file with prerecorded network data. If `path` is a relative path, then it is resolved relative to the current working directory.
### option: BrowserContext.routeFromHAR.notFound
- `notFound` ?<[HarNotFound]<"abort"|"fallback">>
* If set to 'abort' any request not found in the HAR file will be aborted.
* If set to 'fallback' falls through to the next route handler in the handler chain.
Defaults to abort.
### option: BrowserContext.routeFromHAR.update
- `update` ?<boolean>
If specified, updates the given HAR with the actual network information instead of serving from file.
### option: BrowserContext.routeFromHAR.url
- `url` <[string]|[RegExp]>
A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern will be surved from the HAR file. If not specified, all requests are served from the HAR file.
## method: BrowserContext.serviceWorkers ## method: BrowserContext.serviceWorkers
* langs: js, python * langs: js, python
- returns: <[Array]<[Worker]>> - returns: <[Array]<[Worker]>>

View file

@ -94,7 +94,6 @@ Maximum time in milliseconds to wait for the application to start. Defaults to `
### option: Electron.launch.recordhar = %%-context-option-recordhar-%% ### option: Electron.launch.recordhar = %%-context-option-recordhar-%%
### option: Electron.launch.recordharpath = %%-context-option-recordhar-path-%% ### option: Electron.launch.recordharpath = %%-context-option-recordhar-path-%%
### option: Electron.launch.recordHarOmitContent = %%-context-option-recordhar-omit-content-%% ### option: Electron.launch.recordHarOmitContent = %%-context-option-recordhar-omit-content-%%
### option: Electron.launch.har = %%-js-context-option-har-%%
### option: Electron.launch.recordvideo = %%-context-option-recordvideo-%% ### option: Electron.launch.recordvideo = %%-context-option-recordvideo-%%
### option: Electron.launch.recordvideodir = %%-context-option-recordvideo-dir-%% ### option: Electron.launch.recordvideodir = %%-context-option-recordvideo-dir-%%
### option: Electron.launch.recordvideosize = %%-context-option-recordvideo-size-%% ### option: Electron.launch.recordvideosize = %%-context-option-recordvideo-size-%%

View file

@ -2732,6 +2732,35 @@ handler function to route the request.
How often a route should be used. By default it will be used every time. How often a route should be used. By default it will be used every time.
## async method: Page.routeFromHAR
If specified the network requests that are made in the page will be served from the HAR file. Read more about [Replaying from HAR](../network.md#replaying-from-har).
Playwright will not serve requests intercepted by Service Worker from the HAR file. See [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend disabling Service Workers when using request interception by setting [`option: Browser.newContext.serviceWorkers`] to `'block'`.
### param: Page.routeFromHAR.har
- `har` <[path]>
Path to a [HAR](http://www.softwareishard.com/blog/har-12-spec) file with prerecorded network data. If `path` is a relative path, then it is resolved relative to the current working directory.
### option: Page.routeFromHAR.notFound
- `notFound` ?<[HarNotFound]<"abort"|"fallback">>
* If set to 'abort' any request not found in the HAR file will be aborted.
* If set to 'fallback' missing requests will be sent to the network.
Defaults to abort.
### option: Page.routeFromHAR.update
- `update` ?<boolean>
If specified, updates the given HAR with the actual network information instead of serving from file.
### option: Page.routeFromHAR.url
- `url` <[string]|[RegExp]>
A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern will be surved from the HAR file. If not specified, all requests are served from the HAR file.
## async method: Page.screenshot ## async method: Page.screenshot
- returns: <[Buffer]> - returns: <[Buffer]>

View file

@ -59,7 +59,9 @@ Returns the [Frame] that initiated this request.
## method: Request.headers ## method: Request.headers
- returns: <[Object]<[string], [string]>> - returns: <[Object]<[string], [string]>>
**DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use [`method: Request.allHeaders`] instead. An object with the request HTTP headers. The header names are lower-cased.
Note that this method does not return security-related headers, including cookie-related ones.
You can use [`method: Request.allHeaders`] for complete list of headers that include `cookie` information.
## async method: Request.headersArray ## async method: Request.headersArray
- returns: <[Array]<[Object]>> - returns: <[Array]<[Object]>>

View file

@ -30,7 +30,9 @@ Indicates whether this Response was fullfilled by a Service Worker's Fetch Handl
## method: Response.headers ## method: Response.headers
- returns: <[Object]<[string], [string]>> - returns: <[Object]<[string], [string]>>
**DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use [`method: Response.allHeaders`] instead. An object with the response HTTP headers. The header names are lower-cased.
Note that this method does not return security-related headers, including cookie-related ones.
You can use [`method: Response.allHeaders`] for complete list of headers that include `cookie` information.
## async method: Response.headersArray ## async method: Response.headersArray
- returns: <[Array]<[Object]>> - returns: <[Array]<[Object]>>

View file

@ -247,37 +247,6 @@ The file path to save the storage state to. If [`option: path`] is a relative pa
current working directory. If no path is provided, storage current working directory. If no path is provided, storage
state is still returned, but won't be saved to the disk. state is still returned, but won't be saved to the disk.
## js-context-option-har
* langs: js, python
- `har` <[Object]>
- `path` <[path]> Path to a [HAR](http://www.softwareishard.com/blog/har-12-spec) file with prerecorded network data. If `path` is a relative path, then it is resolved relative to the current working directory.
- `fallback` ?<[HarFallback]<"abort"|"continue">> If set to 'abort' any request not found in the HAR file will be aborted. If set to'continue' missing requests will be sent to the network. Defaults to 'abort'.
- `urlFilter` ?<[string]|[RegExp]> A glob pattern or regular expression to match request URL while routing. Only requests with URL matching the pattern will be surved from the HAR file. If not specified, all requests are served from the HAR file.
If specified the network requests that are made in the context will be served from the HAR file. Read more about [Replaying from HAR](../network.md#replaying-from-har).
:::note
Playwright will not serve requests intercepted by Service Worker from the HAR file. See [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend disabling Service Workers when using request interception by setting [`option: Browser.newContext.serviceWorkers`] to `'block'`.
:::
## csharp-java-python-context-option-har-path
* langs: csharp, java, python
- `harPath` <[path]>
Path to a [HAR](http://www.softwareishard.com/blog/har-12-spec) file with prerecorded network data. If the HAR file contains an entry with the matching URL and HTTP method, then the entry's headers, status and body will be used to fulfill the network request. An entry resulting in a redirect will be followed automatically. If `path` is a relative path, then it is resolved relative to the current working directory.
## csharp-java-python-context-option-har-fallback
* langs: csharp, java, python
- `harFallback` ?<[HarFallback]<"abort"|"continue">>
If set to 'abort' any request not found in the HAR file will be aborted. If set to'continue' missing requests will be sent to the network. Defaults to 'abort'.
## csharp-java-python-context-option-har-urlfilter
* langs: csharp, java, python
- `harUrlFilter` ?<[string]|[RegExp]>
A glob pattern or regular expression to match request URL while routing. Only requests with URL matching the pattern will be surved from the HAR file. If not specified, all requests are served from the HAR file.
## context-option-acceptdownloads ## context-option-acceptdownloads
- `acceptDownloads` <[boolean]> - `acceptDownloads` <[boolean]>
@ -592,8 +561,9 @@ Logger sink for Playwright logging.
- `recordHar` <[Object]> - `recordHar` <[Object]>
- `omitContent` ?<[boolean]> Optional setting to control whether to omit request content from the HAR. Defaults to - `omitContent` ?<[boolean]> Optional setting to control whether to omit request content from the HAR. Defaults to
`false`. Deprecated, use `content` policy instead. `false`. Deprecated, use `content` policy instead.
- `content` ?<[HarContentPolicy]<"omit"|"embed"|"attach">> Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. Defaults to `embed`, which stores content inline the HAR file as per HAR specification. - `content` ?<[HarContentPolicy]<"omit"|"embed"|"attach">> Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` is specified, resources are persistet as separate files or entries in the ZIP archive. If `embed` is specified, content is stored inline the HAR file as per HAR specification. Defaults to `attach` for `.zip` output files and to `embed` for all other file extensions.
- `path` <[path]> Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `attach` mode is used by default. - `path` <[path]> Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `content: 'attach'` is used by default.
- `mode` ?<[HarMode]<"full"|"minimal">> When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`.
- `urlFilter` ?<[string]|[RegExp]> A glob or regex pattern to filter requests that are stored in the HAR. When a [`option: baseURL`] via the context options was provided and the passed URL is a path, it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. - `urlFilter` ?<[string]|[RegExp]> A glob or regex pattern to filter requests that are stored in the HAR. When a [`option: baseURL`] via the context options was provided and the passed URL is a path, it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. If not Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. If not
@ -616,6 +586,20 @@ call [`method: BrowserContext.close`] for the HAR to be saved.
Optional setting to control whether to omit request content from the HAR. Defaults to `false`. Optional setting to control whether to omit request content from the HAR. Defaults to `false`.
## context-option-recordhar-content
* langs: csharp, java, python
- alias-python: record_har_content
- `recordHarContent` ?<[HarContentPolicy]<"omit"|"embed"|"attach">>
Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. Defaults to `embed`, which stores content inline the HAR file as per HAR specification.
## context-option-recordhar-mode
* langs: csharp, java, python
- alias-python: record_har_mode
- `recordHarMode` ?<[HarMode]<"full"|"minimal">>
When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`.
## context-option-recordhar-url-filter ## context-option-recordhar-url-filter
* langs: csharp, java, python * langs: csharp, java, python
- alias-python: record_har_url_filter - alias-python: record_har_url_filter
@ -834,13 +818,11 @@ An acceptable perceived color difference in the [YIQ color space](https://en.wik
- %%-context-option-logger-%% - %%-context-option-logger-%%
- %%-context-option-videospath-%% - %%-context-option-videospath-%%
- %%-context-option-videosize-%% - %%-context-option-videosize-%%
- %%-js-context-option-har-%%
- %%-csharp-java-python-context-option-har-path-%%
- %%-csharp-java-python-context-option-har-fallback-%%
- %%-csharp-java-python-context-option-har-urlfilter-%%
- %%-context-option-recordhar-%% - %%-context-option-recordhar-%%
- %%-context-option-recordhar-path-%% - %%-context-option-recordhar-path-%%
- %%-context-option-recordhar-omit-content-%% - %%-context-option-recordhar-omit-content-%%
- %%-context-option-recordhar-content-%%
- %%-context-option-recordhar-mode-%%
- %%-context-option-recordhar-url-filter-%% - %%-context-option-recordhar-url-filter-%%
- %%-context-option-recordvideo-%% - %%-context-option-recordvideo-%%
- %%-context-option-recordvideo-dir-%% - %%-context-option-recordvideo-dir-%%

View file

@ -143,7 +143,7 @@ Alternatively, you can use [Command line tools](./cli.md#install-system-dependen
pool: pool:
vmImage: 'ubuntu-20.04' vmImage: 'ubuntu-20.04'
container: mcr.microsoft.com/playwright:v1.23.0-focal container: mcr.microsoft.com/playwright:v1.23.4-focal
steps: steps:
... ...
@ -157,7 +157,7 @@ Running Playwright on CircleCI requires the following steps:
```yml ```yml
docker: docker:
- image: mcr.microsoft.com/playwright:v1.23.0-focal - image: mcr.microsoft.com/playwright:v1.23.4-focal
environment: environment:
NODE_ENV: development # Needed if playwright is in `devDependencies` NODE_ENV: development # Needed if playwright is in `devDependencies`
``` ```
@ -179,7 +179,7 @@ to run tests on Jenkins.
```groovy ```groovy
pipeline { pipeline {
agent { docker { image 'mcr.microsoft.com/playwright:v1.23.0-focal' } } agent { docker { image 'mcr.microsoft.com/playwright:v1.23.4-focal' } }
stages { stages {
stage('e2e-tests') { stage('e2e-tests') {
steps { steps {
@ -196,7 +196,7 @@ pipeline {
Bitbucket Pipelines can use public [Docker images as build environments](https://confluence.atlassian.com/bitbucket/use-docker-images-as-build-environments-792298897.html). To run Playwright tests on Bitbucket, use our public Docker image ([see Dockerfile](./docker.md)). Bitbucket Pipelines can use public [Docker images as build environments](https://confluence.atlassian.com/bitbucket/use-docker-images-as-build-environments-792298897.html). To run Playwright tests on Bitbucket, use our public Docker image ([see Dockerfile](./docker.md)).
```yml ```yml
image: mcr.microsoft.com/playwright:v1.23.0-focal image: mcr.microsoft.com/playwright:v1.23.4-focal
``` ```
### GitLab CI ### GitLab CI
@ -209,7 +209,7 @@ stages:
tests: tests:
stage: test stage: test
image: mcr.microsoft.com/playwright:v1.23.0-focal image: mcr.microsoft.com/playwright:v1.23.4-focal
script: script:
... ...
``` ```

View file

@ -14,19 +14,19 @@ This image is published on [Docker Hub].
### Pull the image ### Pull the image
```bash js ```bash js
docker pull mcr.microsoft.com/playwright:v1.23.0-focal docker pull mcr.microsoft.com/playwright:v1.23.4-focal
``` ```
```bash python ```bash python
docker pull mcr.microsoft.com/playwright/python:v1.23.0-focal docker pull mcr.microsoft.com/playwright/python:v1.23.4-focal
``` ```
```bash csharp ```bash csharp
docker pull mcr.microsoft.com/playwright/dotnet:v1.23.0-focal docker pull mcr.microsoft.com/playwright/dotnet:v1.23.4-focal
``` ```
```bash java ```bash java
docker pull mcr.microsoft.com/playwright/java:v1.23.0-focal docker pull mcr.microsoft.com/playwright/java:v1.23.4-focal
``` ```
### Run the image ### Run the image
@ -38,19 +38,19 @@ By default, the Docker image will use the `root` user to run the browsers. This
On trusted websites, you can avoid creating a separate user and use root for it since you trust the code which will run on the browsers. On trusted websites, you can avoid creating a separate user and use root for it since you trust the code which will run on the browsers.
```bash js ```bash js
docker run -it --rm --ipc=host mcr.microsoft.com/playwright:v1.23.0-focal /bin/bash docker run -it --rm --ipc=host mcr.microsoft.com/playwright:v1.23.4-focal /bin/bash
``` ```
```bash python ```bash python
docker run -it --rm --ipc=host mcr.microsoft.com/playwright/python:v1.23.0-focal /bin/bash docker run -it --rm --ipc=host mcr.microsoft.com/playwright/python:v1.23.4-focal /bin/bash
``` ```
```bash csharp ```bash csharp
docker run -it --rm --ipc=host mcr.microsoft.com/playwright/dotnet:v1.23.0-focal /bin/bash docker run -it --rm --ipc=host mcr.microsoft.com/playwright/dotnet:v1.23.4-focal /bin/bash
``` ```
```bash java ```bash java
docker run -it --rm --ipc=host mcr.microsoft.com/playwright/java:v1.23.0-focal /bin/bash docker run -it --rm --ipc=host mcr.microsoft.com/playwright/java:v1.23.4-focal /bin/bash
``` ```
#### Crawling and scraping #### Crawling and scraping
@ -58,19 +58,19 @@ docker run -it --rm --ipc=host mcr.microsoft.com/playwright/java:v1.23.0-focal /
On untrusted websites, it's recommended to use a separate user for launching the browsers in combination with the seccomp profile. Inside the container or if you are using the Docker image as a base image you have to use `adduser` for it. On untrusted websites, it's recommended to use a separate user for launching the browsers in combination with the seccomp profile. Inside the container or if you are using the Docker image as a base image you have to use `adduser` for it.
```bash js ```bash js
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright:v1.23.0-focal /bin/bash docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright:v1.23.4-focal /bin/bash
``` ```
```bash python ```bash python
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/python:v1.23.0-focal /bin/bash docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/python:v1.23.4-focal /bin/bash
``` ```
```bash csharp ```bash csharp
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/dotnet:v1.23.0-focal /bin/bash docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/dotnet:v1.23.4-focal /bin/bash
``` ```
```bash java ```bash java
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/java:v1.23.0-focal /bin/bash docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/java:v1.23.4-focal /bin/bash
``` ```
[`seccomp_profile.json`](https://github.com/microsoft/playwright/blob/main/utils/docker/seccomp_profile.json) is needed to run Chromium with sandbox. This is a [default Docker seccomp profile](https://github.com/docker/engine/blob/d0d99b04cf6e00ed3fc27e81fc3d94e7eda70af3/profiles/seccomp/default.json) with extra user namespace cloning permissions: [`seccomp_profile.json`](https://github.com/microsoft/playwright/blob/main/utils/docker/seccomp_profile.json) is needed to run Chromium with sandbox. This is a [default Docker seccomp profile](https://github.com/docker/engine/blob/d0d99b04cf6e00ed3fc27e81fc3d94e7eda70af3/profiles/seccomp/default.json) with extra user namespace cloning permissions:

View file

@ -59,7 +59,7 @@ public class Example {
<dependency> <dependency>
<groupId>com.microsoft.playwright</groupId> <groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId> <artifactId>playwright</artifactId>
<version>1.17.1</version> <version>1.23.0</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>

View file

@ -740,7 +740,7 @@ await context.close();
```java ```java
BrowserContext context = browser.newContext(new Browser.NewContextOptions() BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setRecordHarPath(Paths.get("example.har")) .setRecordHarPath(Paths.get("example.har"))
.setRecordHarUrlFilter("**/api/**"); .setRecordHarUrlFilter("**/api/**"));
// ... Perform actions ... // ... Perform actions ...
@ -780,61 +780,43 @@ await context.CloseAsync();
### Replaying from HAR ### Replaying from HAR
Pass [`option: har`] option to the [`method: Browser.newContext`] method to use matching responses from the [HAR](http://www.softwareishard.com/blog/har-12-spec/) file. Use [`method: Page.routeFromHAR`] or [`method: BrowserContext.routeFromHAR`] to serve matching responses from the [HAR](http://www.softwareishard.com/blog/har-12-spec/) file.
```js ```js
// Replay API requests from HAR. // Replay API requests from HAR.
// Either use a matching response from the HAR, // Either use a matching response from the HAR,
// or abort the request if nothing matches. // or abort the request if nothing matches.
const context = await browser.newContext({ har: { path: 'example.har' } }); await page.routeFromHAR('example.har');
``` ```
```java ```java
// Either use a matching response from the HAR, // Either use a matching response from the HAR,
// or abort the request if nothing matches. // or abort the request if nothing matches.
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setHarPath(Paths.get("example.har"))); page.routeFromHAR(Paths.get("example.har"));
Page page = context.newPage();
page.navigate("https://example.com");
``` ```
```python async ```python async
# Either use a matching response from the HAR, # Either use a matching response from the HAR,
# or abort the request if nothing matches. # or abort the request if nothing matches.
context = await browser.new_context( await page.route_from_har("example.har")
har_path = "example.har"
)
page = await context.new_page()
await page.goto("https://example.com")
``` ```
```python sync ```python sync
# Either use a matching response from the HAR, # Either use a matching response from the HAR,
# or abort the request if nothing matches. # or abort the request if nothing matches.
context = browser.new_context( page.route_from_har("example.har")
har_path="example.har"
)
page = context.new_page()
page.goto("https://example.com")
``` ```
```csharp ```csharp
// Either use a matching response from the HAR, // Either use a matching response from the HAR,
// or abort the request if nothing matches. // or abort the request if nothing matches.
var context = await Browser.NewContextAsync(new () { await context.RouteFromHARAsync("example.har");
HarPath = "example.har"
});
var page = await context.NewPageAsync();
await page.GotoAsync("https://example.com");
``` ```
HAR replay matches URL and HTTP method strictly. For POST requests, it also matches POST payloads strictly. If multiple recordings match a request, the one with the most matching headers is picked. An entry resulting in a redirect will be followed automatically. HAR replay matches URL and HTTP method strictly. For POST requests, it also matches POST payloads strictly. If multiple recordings match a request, the one with the most matching headers is picked. An entry resulting in a redirect will be followed automatically.
Similar to when recording, if given HAR file name ends with `.zip`, it is considered an archive containing the HAR file along with network payloads stored as separate entries. You can also extract this archive, edit payloads or HAR log manually and point to the extracted har file. All the payloads will be resolved relative to the extracted har file on the file system. Similar to when recording, if given HAR file name ends with `.zip`, it is considered an archive containing the HAR file along with network payloads stored as separate entries. You can also extract this archive, edit payloads or HAR log manually and point to the extracted har file. All the payloads will be resolved relative to the extracted har file on the file system.
### API reference
- [`method: Browser.newContext`]
- [`method: Route.fulfill`]
<br/> <br/>
## WebSockets ## WebSockets

View file

@ -5,6 +5,124 @@ title: "Release notes"
<!-- TOC --> <!-- TOC -->
## Version 1.23
### API Testing
Playwright for .NET 1.23 introduces new [API Testing](./api/class-apirequestcontext) that lets you send requests to the server directly from .NET!
Now you can:
- test your server API
- prepare server side state before visiting the web application in a test
- validate server side post-conditions after running some actions in the browser
To do a request on behalf of Playwright's Page, use **new [`property: Page.request`] API**:
```csharp
// Do a GET request on behalf of page
var response = await Page.APIRequest.GetAsync("http://example.com/foo.json");
Console.WriteLine(response.Status);
Console.WriteLine(response.StatusText);
Console.WriteLine(response.Ok);
Console.WriteLine(response.Headers["Content-Type"]);
Console.WriteLine(await response.TextAsync());
Console.WriteLine((await response.JsonAsync())?.GetProperty("foo").GetString());
```
Read more about it in our [API testing guide](./api-testing).
### Network Replay
Now you can record network traffic into a HAR file and re-use this traffic in your tests.
To record network into HAR file:
```bash
pwsh bin\Debug\netX\playwright.ps1 open --save-har=example.har --save-har-glob="**/api/**" https://example.com
```
Alternatively, you can record HAR programmatically:
```csharp
var context = await browser.NewContextAsync(new ()
{
RecordHarPath = harPath,
RecordHarUrlFilterString = "**/api/**",
});
// ... Perform actions ...
// Close context to ensure HAR is saved to disk.
context.CloseAsync();
```
Use the new methods [`method: Page.routeFromHAR`] or [`method: BrowserContext.routeFromHAR`] to serve matching responses from the [HAR](http://www.softwareishard.com/blog/har-12-spec/) file:
```csharp
await context.RouteFromHARAsync("example.har");
```
Read more in [our documentation](./network#record-and-replay-requests).
### Advanced Routing
You can now use [`method: Route.fallback`] to defer routing to other handlers.
Consider the following example:
```csharp
// Remove a header from all requests.
await page.RouteAsync("**/*", async route =>
{
var headers = route.Request.Headers;
headers.Remove("X-Secret");
await route.ContinueAsync(new () { Headers = headers });
});
// Abort all images.
await page.RouteAsync("**/*", async route =>
{
if (route.Request.ResourceType == "image")
{
await route.AbortAsync();
}
else
{
await route.FallbackAsync();
}
});
```
Note that the new methods [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`] also participate in routing and could be deferred to.
### Web-First Assertions Update
* New method [`method: LocatorAssertions.toHaveValues`] that asserts all selected values of `<select multiple>` element.
* Methods [`method: LocatorAssertions.toContainText`] and [`method: LocatorAssertions.toHaveText`] now accept `ignoreCase` option.
### Miscellaneous
* If there's a service worker that's in your way, you can now easily disable it with a new context option `serviceWorkers`:
```csharp
var context = await Browser.NewContextAsync(new()
{
ServiceWorkers = ServiceWorkerPolicy.Block
});
```
* Using `.zip` path for `recordHar` context option automatically zips the resulting HAR:
```csharp
var context = await Browser.NewContextAsync(new() { RecordHarPath = "example.har.zip" });
```
* If you intend to edit HAR by hand, consider using the `"minimal"` HAR recording mode
that only records information that is essential for replaying:
```csharp
var context = await Browser.NewContextAsync(new() { RecordHarPath = "example.har", RecordHarMode = HarMode.Minimal });
```
* Playwright now runs on Ubuntu 22 amd64 and Ubuntu 22 arm64.
* Playwright for .NET now supports **linux-arm64** and provides a **arm64 Ubuntu 20.04 Docker image** for it.
## Version 1.22 ## Version 1.22
### Highlights ### Highlights

View file

@ -5,6 +5,93 @@ title: "Release notes"
<!-- TOC --> <!-- TOC -->
## Version 1.23
### Network Replay
Now you can record network traffic into a HAR file and re-use this traffic in your tests.
To record network into HAR file:
```bash
mvn exec:java -e -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="open --save-har=example.har --save-har-glob='**/api/**' https://example.com"
```
Alternatively, you can record HAR programmatically:
```java
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setRecordHarPath(Paths.get("example.har"))
.setRecordHarUrlFilter("**/api/**"));
// ... Perform actions ...
// Close context to ensure HAR is saved to disk.
context.close();
```
Use the new methods [`method: Page.routeFromHAR`] or [`method: BrowserContext.routeFromHAR`] to serve matching responses from the [HAR](http://www.softwareishard.com/blog/har-12-spec/) file:
```java
context.routeFromHAR(Paths.get("example.har"));
```
Read more in [our documentation](./network#record-and-replay-requests).
### Advanced Routing
You can now use [`method: Route.fallback`] to defer routing to other handlers.
Consider the following example:
```java
// Remove a header from all requests.
page.route("**/*", route -> {
Map<String, String> headers = new HashMap<>(route.request().headers());
headers.remove("X-Secret");
route.resume(new Route.ResumeOptions().setHeaders(headers));
});
// Abort all images.
page.route("**/*", route -> {
if ("image".equals(route.request().resourceType()))
route.abort();
else
route.fallback();
});
```
Note that the new methods [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`] also participate in routing and could be deferred to.
### Web-First Assertions Update
* New method [`method: LocatorAssertions.toHaveValues`] that asserts all selected values of `<select multiple>` element.
* Methods [`method: LocatorAssertions.toContainText`] and [`method: LocatorAssertions.toHaveText`] now accept `ignoreCase` option.
### Miscellaneous
* If there's a service worker that's in your way, you can now easily disable it with a new context option `serviceWorkers`:
```java
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setServiceWorkers(ServiceWorkerPolicy.BLOCK));
```
* Using `.zip` path for `recordHar` context option automatically zips the resulting HAR:
```java
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setRecordHarPath(Paths.get("example.har.zip")));
```
* If you intend to edit HAR by hand, consider using the `"minimal"` HAR recording mode
that only records information that is essential for replaying:
```java
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setRecordHarPath(Paths.get("example.har"))
.setRecordHarMode(HarMode.MINIMAL));
```
* Playwright now runs on Ubuntu 22 amd64 and Ubuntu 22 arm64.
## Version 1.22 ## Version 1.22
### Highlights ### Highlights

View file

@ -5,6 +5,118 @@ title: "Release notes"
<!-- TOC --> <!-- TOC -->
## Version 1.23
### Network Replay
Now you can record network traffic into a HAR file and re-use this traffic in your tests.
To record network into HAR file:
```bash
npx playwright open --save-har=github.har.zip https://github.com/microsoft
```
Alternatively, you can record HAR programmatically:
```ts
const context = await browser.newContext({
recordHar: { path: 'github.har.zip' }
});
// ... do stuff ...
await context.close();
```
Use the new methods [`method: Page.routeFromHAR`] or [`method: BrowserContext.routeFromHAR`] to serve matching responses from the [HAR](http://www.softwareishard.com/blog/har-12-spec/) file:
```ts
await context.routeFromHAR('github.har.zip');
```
Read more in [our documentation](./network#record-and-replay-requests).
### Advanced Routing
You can now use [`method: Route.fallback`] to defer routing to other handlers.
Consider the following example:
```ts
// Remove a header from all requests.
test.beforeEach(async ({ page }) => {
await page.route('**/*', async route => {
const headers = await route.request().allHeaders();
delete headers['if-none-match'];
route.fallback({ headers });
});
});
test('should work', async ({ page }) => {
await page.route('**/*', route => {
if (route.request().resourceType() === 'image')
route.abort();
else
route.fallback();
});
});
```
Note that the new methods [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`] also participate in routing and could be deferred to.
### Web-First Assertions Update
* New method [`method: LocatorAssertions.toHaveValues`] that asserts all selected values of `<select multiple>` element.
* Methods [`method: LocatorAssertions.toContainText`] and [`method: LocatorAssertions.toHaveText`] now accept `ignoreCase` option.
### Component Tests Update
* Support for Vue2 via the [`@playwright/experimental-ct-vue2`](https://www.npmjs.com/package/@playwright/experimental-ct-vue2) package.
* Support for component tests for [create-react-app](https://www.npmjs.com/package/create-react-app) with components in `.js` files.
Read more about [component testing with Playwright](./test-components).
### Miscellaneous
* If there's a service worker that's in your way, you can now easily disable it with a new context option `serviceWorkers`:
```ts
// playwright.config.ts
export default {
use: {
serviceWorkers: 'block',
}
}
```
* Using `.zip` path for `recordHar` context option automatically zips the resulting HAR:
```ts
const context = await browser.newContext({
recordHar: {
path: 'github.har.zip',
}
});
```
* If you intend to edit HAR by hand, consider using the `"minimal"` HAR recording mode
that only records information that is essential for replaying:
```ts
const context = await browser.newContext({
recordHar: {
path: 'github.har',
mode: 'minimal',
}
});
```
* Playwright now runs on Ubuntu 22 amd64 and Ubuntu 22 arm64. We also publish new docker image `mcr.microsoft.com/playwright:v1.23.4-jammy`.
### ⚠️ Breaking Changes ⚠️
WebServer is now considered "ready" if request to the specified port has any of the following HTTP status codes:
* `200-299`
* `300-399` (new)
* `400`, `401`, `402`, `403` (new)
## Version 1.22 ## Version 1.22
### Highlights ### Highlights

View file

@ -5,6 +5,137 @@ title: "Release notes"
<!-- TOC --> <!-- TOC -->
## Version 1.23
### Network Replay
Now you can record network traffic into a HAR file and re-use this traffic in your tests.
To record network into HAR file:
```bash
npx playwright open --save-har=github.har.zip https://github.com/microsoft
```
Alternatively, you can record HAR programmatically:
```python async
context = await browser.new_context(record_har_path="github.har.zip")
# ... do stuff ...
await context.close()
```
```python sync
context = browser.new_context(record_har_path="github.har.zip")
# ... do stuff ...
context.close()
```
Use the new methods [`method: Page.routeFromHAR`] or [`method: BrowserContext.routeFromHAR`] to serve matching responses from the [HAR](http://www.softwareishard.com/blog/har-12-spec/) file:
```python async
await context.route_from_har("github.har.zip")
```
```python sync
context.route_from_har("github.har.zip")
```
Read more in [our documentation](./network#record-and-replay-requests).
### Advanced Routing
You can now use [`method: Route.fallback`] to defer routing to other handlers.
Consider the following example:
```python async
# Remove a header from all requests
async def remove_header_handler(route: Route) -> None:
headers = await route.request.all_headers()
if "if-none-match" in headers:
del headers["if-none-match"]
await route.fallback(headers=headers)
await page.route("**/*", remove_header_handler)
# Abort all images
async def abort_images_handler(route: Route) -> None:
if route.request.resource_type == "image":
await route.abort()
else:
await route.fallback()
await page.route("**/*", abort_images_handler)
```
```python sync
# Remove a header from all requests
def remove_header_handler(route: Route) -> None:
headers = route.request.all_headers()
if "if-none-match" in headers:
del headers["if-none-match"]
route.fallback(headers=headers)
page.route("**/*", remove_header_handler)
# Abort all images
def abort_images_handler(route: Route) -> None:
if route.request.resource_type == "image":
route.abort()
else:
route.fallback()
page.route("**/*", abort_images_handler)
```
Note that the new methods [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`] also participate in routing and could be deferred to.
### Web-First Assertions Update
* New method [`method: LocatorAssertions.toHaveValues`] that asserts all selected values of `<select multiple>` element.
* Methods [`method: LocatorAssertions.toContainText`] and [`method: LocatorAssertions.toHaveText`] now accept `ignore_case` option.
### Miscellaneous
* If there's a service worker that's in your way, you can now easily disable it with a new context option `service_workers`:
```python async
context = await browser.new_context(service_workers="block")
page = await context.new_page()
```
```python sync
context = browser.new_context(service_workers="block")
page = context.new_page()
```
* Using `.zip` path for `recordHar` context option automatically zips the resulting HAR:
```python async
context = await browser.new_context(record_har_path="github.har.zip")
```
```python sync
context = browser.new_context(record_har_path="github.har.zip")
```
* If you intend to edit HAR by hand, consider using the `"minimal"` HAR recording mode
that only records information that is essential for replaying:
```python async
context = await browser.new_context(record_har_mode="minimal", record_har_path="har.har")
```
```python sync
context = browser.new_context(record_har_mode="minimal", record_har_path="har.har")
```
* Playwright now runs on Ubuntu 22 amd64 and Ubuntu 22 arm64.
## Version 1.22 ## Version 1.22
### Highlights ### Highlights

View file

@ -129,8 +129,6 @@ Options used to create the context, as passed to [`method: Browser.newContext`].
## property: TestOptions.geolocation = %%-context-option-geolocation-%% ## property: TestOptions.geolocation = %%-context-option-geolocation-%%
## property: TestOptions.har = %%-js-context-option-har-%%
## property: TestOptions.hasTouch = %%-context-option-hastouch-%% ## property: TestOptions.hasTouch = %%-context-option-hastouch-%%
## property: TestOptions.headless = %%-browser-option-headless-%% ## property: TestOptions.headless = %%-browser-option-headless-%%

View file

@ -56,7 +56,7 @@ The snapshot name `example-test-1-chromium-darwin.png` consists of a few parts:
If you are not on the same operating system as your CI system, you can use Docker to generate/update the screenshots: If you are not on the same operating system as your CI system, you can use Docker to generate/update the screenshots:
```bash ```bash
docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.23.0-focal /bin/bash docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.23.4-focal /bin/bash
npm install npm install
npx playwright test --update-snapshots npx playwright test --update-snapshots
``` ```

60
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "playwright-internal", "name": "playwright-internal",
"version": "1.23.0-next", "version": "1.23.4",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "playwright-internal", "name": "playwright-internal",
"version": "1.23.0-next", "version": "1.23.4",
"license": "Apache-2.0", "license": "Apache-2.0",
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
@ -6459,11 +6459,11 @@
"version": "0.0.0" "version": "0.0.0"
}, },
"packages/playwright": { "packages/playwright": {
"version": "1.23.0-next", "version": "1.23.4",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.23.0-next" "playwright-core": "1.23.4"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -6473,11 +6473,11 @@
} }
}, },
"packages/playwright-chromium": { "packages/playwright-chromium": {
"version": "1.23.0-next", "version": "1.23.4",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.23.0-next" "playwright-core": "1.23.4"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -6487,7 +6487,7 @@
} }
}, },
"packages/playwright-core": { "packages/playwright-core": {
"version": "1.23.0-next", "version": "1.23.4",
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -6498,10 +6498,10 @@
}, },
"packages/playwright-ct-react": { "packages/playwright-ct-react": {
"name": "@playwright/experimental-ct-react", "name": "@playwright/experimental-ct-react",
"version": "1.23.0-next", "version": "1.23.4",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.4",
"@vitejs/plugin-react": "^1.0.7", "@vitejs/plugin-react": "^1.0.7",
"vite": "^2.9.5" "vite": "^2.9.5"
}, },
@ -6511,10 +6511,10 @@
}, },
"packages/playwright-ct-svelte": { "packages/playwright-ct-svelte": {
"name": "@playwright/experimental-ct-svelte", "name": "@playwright/experimental-ct-svelte",
"version": "1.23.0-next", "version": "1.23.4",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.4",
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.30", "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
"vite": "^2.9.5" "vite": "^2.9.5"
}, },
@ -6524,10 +6524,10 @@
}, },
"packages/playwright-ct-vue": { "packages/playwright-ct-vue": {
"name": "@playwright/experimental-ct-vue", "name": "@playwright/experimental-ct-vue",
"version": "1.23.0-next", "version": "1.23.4",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.4",
"@vitejs/plugin-vue": "^2.3.1", "@vitejs/plugin-vue": "^2.3.1",
"vite": "^2.9.5" "vite": "^2.9.5"
}, },
@ -6575,10 +6575,10 @@
}, },
"packages/playwright-ct-vue2": { "packages/playwright-ct-vue2": {
"name": "@playwright/experimental-ct-vue2", "name": "@playwright/experimental-ct-vue2",
"version": "1.23.0-next", "version": "1.23.4",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.4",
"vite": "^2.9.5", "vite": "^2.9.5",
"vite-plugin-vue2": "^2.0.1" "vite-plugin-vue2": "^2.0.1"
}, },
@ -6590,11 +6590,11 @@
} }
}, },
"packages/playwright-firefox": { "packages/playwright-firefox": {
"version": "1.23.0-next", "version": "1.23.4",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.23.0-next" "playwright-core": "1.23.4"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -6605,11 +6605,11 @@
}, },
"packages/playwright-test": { "packages/playwright-test": {
"name": "@playwright/test", "name": "@playwright/test",
"version": "1.23.0-next", "version": "1.23.4",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"playwright-core": "1.23.0-next" "playwright-core": "1.23.4"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -6619,11 +6619,11 @@
} }
}, },
"packages/playwright-webkit": { "packages/playwright-webkit": {
"version": "1.23.0-next", "version": "1.23.4",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.23.0-next" "playwright-core": "1.23.4"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -7437,7 +7437,7 @@
"@playwright/experimental-ct-react": { "@playwright/experimental-ct-react": {
"version": "file:packages/playwright-ct-react", "version": "file:packages/playwright-ct-react",
"requires": { "requires": {
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.4",
"@vitejs/plugin-react": "^1.0.7", "@vitejs/plugin-react": "^1.0.7",
"vite": "^2.9.5" "vite": "^2.9.5"
} }
@ -7445,7 +7445,7 @@
"@playwright/experimental-ct-svelte": { "@playwright/experimental-ct-svelte": {
"version": "file:packages/playwright-ct-svelte", "version": "file:packages/playwright-ct-svelte",
"requires": { "requires": {
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.4",
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.30", "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
"vite": "^2.9.5" "vite": "^2.9.5"
} }
@ -7453,7 +7453,7 @@
"@playwright/experimental-ct-vue": { "@playwright/experimental-ct-vue": {
"version": "file:packages/playwright-ct-vue", "version": "file:packages/playwright-ct-vue",
"requires": { "requires": {
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.4",
"@vitejs/plugin-vue": "^2.3.1", "@vitejs/plugin-vue": "^2.3.1",
"vite": "^2.9.5" "vite": "^2.9.5"
}, },
@ -7492,7 +7492,7 @@
"@playwright/experimental-ct-vue2": { "@playwright/experimental-ct-vue2": {
"version": "file:packages/playwright-ct-vue2", "version": "file:packages/playwright-ct-vue2",
"requires": { "requires": {
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.4",
"vite": "^2.9.5", "vite": "^2.9.5",
"vite-plugin-vue2": "^2.0.1", "vite-plugin-vue2": "^2.0.1",
"vue": "^2.6.14" "vue": "^2.6.14"
@ -7502,7 +7502,7 @@
"version": "file:packages/playwright-test", "version": "file:packages/playwright-test",
"requires": { "requires": {
"@types/node": "*", "@types/node": "*",
"playwright-core": "1.23.0-next" "playwright-core": "1.23.4"
} }
}, },
"@rollup/pluginutils": { "@rollup/pluginutils": {
@ -10311,13 +10311,13 @@
"playwright": { "playwright": {
"version": "file:packages/playwright", "version": "file:packages/playwright",
"requires": { "requires": {
"playwright-core": "1.23.0-next" "playwright-core": "1.23.4"
} }
}, },
"playwright-chromium": { "playwright-chromium": {
"version": "file:packages/playwright-chromium", "version": "file:packages/playwright-chromium",
"requires": { "requires": {
"playwright-core": "1.23.0-next" "playwright-core": "1.23.4"
} }
}, },
"playwright-core": { "playwright-core": {
@ -10326,13 +10326,13 @@
"playwright-firefox": { "playwright-firefox": {
"version": "file:packages/playwright-firefox", "version": "file:packages/playwright-firefox",
"requires": { "requires": {
"playwright-core": "1.23.0-next" "playwright-core": "1.23.4"
} }
}, },
"playwright-webkit": { "playwright-webkit": {
"version": "file:packages/playwright-webkit", "version": "file:packages/playwright-webkit",
"requires": { "requires": {
"playwright-core": "1.23.0-next" "playwright-core": "1.23.4"
} }
}, },
"postcss": { "postcss": {

View file

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

View file

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

View file

@ -3,15 +3,15 @@
"browsers": [ "browsers": [
{ {
"name": "chromium", "name": "chromium",
"revision": "1011", "revision": "1012",
"installByDefault": true, "installByDefault": true,
"browserVersion": "103.0.5060.53" "browserVersion": "104.0.5112.20"
}, },
{ {
"name": "chromium-with-symbols", "name": "chromium-with-symbols",
"revision": "1011", "revision": "1012",
"installByDefault": false, "installByDefault": false,
"browserVersion": "103.0.5060.53" "browserVersion": "104.0.5112.20"
}, },
{ {
"name": "chromium-tip-of-tree", "name": "chromium-tip-of-tree",
@ -33,7 +33,7 @@
}, },
{ {
"name": "webkit", "name": "webkit",
"revision": "1666", "revision": "1668",
"installByDefault": true, "installByDefault": true,
"revisionOverrides": { "revisionOverrides": {
"mac10.14": "1446", "mac10.14": "1446",

View file

@ -1,6 +1,6 @@
{ {
"name": "playwright-core", "name": "playwright-core",
"version": "1.23.0-next", "version": "1.23.4",
"description": "A high-level API to automate web browsers", "description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -38,7 +38,6 @@
"./lib/server": "./lib/server/index.js", "./lib/server": "./lib/server/index.js",
"./lib/utilsBundle": "./lib/utilsBundle.js", "./lib/utilsBundle": "./lib/utilsBundle.js",
"./lib/zipBundle": "./lib/zipBundle.js", "./lib/zipBundle": "./lib/zipBundle.js",
"./types/har": "./types/har.d.ts",
"./types/protocol": "./types/protocol.d.ts", "./types/protocol": "./types/protocol.d.ts",
"./types/structs": "./types/structs.d.ts" "./types/structs": "./types/structs.d.ts"
}, },

View file

@ -468,7 +468,7 @@ async function launchContext(options: Options, headless: boolean, executablePath
// HAR // HAR
if (options.saveHar) { if (options.saveHar) {
contextOptions.recordHar = { path: path.resolve(process.cwd(), options.saveHar) }; contextOptions.recordHar = { path: path.resolve(process.cwd(), options.saveHar), mode: 'minimal' };
if (options.saveHarGlob) if (options.saveHarGlob)
contextOptions.recordHar.urlFilter = options.saveHarGlob; contextOptions.recordHar.urlFilter = options.saveHarGlob;
contextOptions.serviceWorkers = 'block'; contextOptions.serviceWorkers = 'block';

View file

@ -24,7 +24,6 @@ import { isSafeCloseError, kBrowserClosedError } from '../common/errors';
import type * as api from '../../types/types'; import type * as api from '../../types/types';
import { CDPSession } from './cdpSession'; import { CDPSession } from './cdpSession';
import type { BrowserType } from './browserType'; import type { BrowserType } from './browserType';
import { HarRouter } from './harRouter';
export class Browser extends ChannelOwner<channels.BrowserChannel> implements api.Browser { export class Browser extends ChannelOwner<channels.BrowserChannel> implements api.Browser {
readonly _contexts = new Set<BrowserContext>(); readonly _contexts = new Set<BrowserContext>();
@ -61,14 +60,12 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> { async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
options = { ...this._browserType._defaultContextOptions, ...options }; options = { ...this._browserType._defaultContextOptions, ...options };
const harRouter = options.har ? await HarRouter.create(this._connection.localUtils(), options.har) : null;
const contextOptions = await prepareBrowserContextParams(options); const contextOptions = await prepareBrowserContextParams(options);
const context = BrowserContext.from((await this._channel.newContext(contextOptions)).context); const context = BrowserContext.from((await this._channel.newContext(contextOptions)).context);
context._options = contextOptions; context._options = contextOptions;
this._contexts.add(context); this._contexts.add(context);
context._logger = options.logger || this._logger; context._logger = options.logger || this._logger;
context._setBrowserType(this._browserType); context._setBrowserType(this._browserType);
harRouter?.addRoute(context);
await this._browserType._onDidCreateContext?.(context); await this._browserType._onDidCreateContext?.(context);
return context; return context;
} }

View file

@ -40,6 +40,7 @@ import { Artifact } from './artifact';
import { APIRequestContext } from './fetch'; import { APIRequestContext } from './fetch';
import { createInstrumentation } from './clientInstrumentation'; import { createInstrumentation } from './clientInstrumentation';
import { rewriteErrorMessage } from '../utils/stackTrace'; import { rewriteErrorMessage } from '../utils/stackTrace';
import { HarRouter } from './harRouter';
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext { export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext {
_pages = new Set<Page>(); _pages = new Set<Page>();
@ -57,6 +58,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
readonly _backgroundPages = new Set<Page>(); readonly _backgroundPages = new Set<Page>();
readonly _serviceWorkers = new Set<Worker>(); readonly _serviceWorkers = new Set<Worker>();
readonly _isChromium: boolean; readonly _isChromium: boolean;
private _harRecorders = new Map<string, { path: string, content: 'embed' | 'attach' | 'omit' | undefined }>();
static from(context: channels.BrowserContextChannel): BrowserContext { static from(context: channels.BrowserContextChannel): BrowserContext {
return (context as any)._object; return (context as any)._object;
@ -99,6 +101,8 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
_setBrowserType(browserType: BrowserType) { _setBrowserType(browserType: BrowserType) {
this._browserType = browserType; this._browserType = browserType;
browserType._contexts.add(this); browserType._contexts.add(this);
if (this._options.recordHar)
this._harRecorders.set('', { path: this._options.recordHar.path, content: this._options.recordHar.content });
} }
private _onPage(page: Page): void { private _onPage(page: Page): void {
@ -144,8 +148,10 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
} }
async _onRoute(route: network.Route, request: network.Request) { async _onRoute(route: network.Route, request: network.Request) {
const routeHandlers = this._routes.filter(r => r.matches(request.url())); const routeHandlers = this._routes.slice();
for (const routeHandler of routeHandlers) { for (const routeHandler of routeHandlers) {
if (!routeHandler.matches(request.url()))
continue;
if (routeHandler.willExpire()) if (routeHandler.willExpire())
this._routes.splice(this._routes.indexOf(routeHandler), 1); this._routes.splice(this._routes.indexOf(routeHandler), 1);
const handled = await routeHandler.handle(route, request); const handled = await routeHandler.handle(route, request);
@ -267,6 +273,28 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
await this._channel.setNetworkInterceptionEnabled({ enabled: true }); await this._channel.setNetworkInterceptionEnabled({ enabled: true });
} }
async _recordIntoHAR(har: string, page: Page | null, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean } = {}): Promise<void> {
const { harId } = await this._channel.harStart({
page: page?._channel,
options: prepareRecordHarOptions({
path: har,
content: 'attach',
mode: 'minimal',
urlFilter: options.url
})!
});
this._harRecorders.set(harId, { path: har, content: 'attach' });
}
async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean } = {}): Promise<void> {
if (options.update) {
await this._recordIntoHAR(har, null, options);
return;
}
const harRouter = await HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', { urlMatch: options.url });
harRouter.addContextRoute(this);
}
async unroute(url: URLMatch, handler?: network.RouteHandlerCallback): Promise<void> { async unroute(url: URLMatch, handler?: network.RouteHandlerCallback): Promise<void> {
this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler)); this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler));
if (!this._routes.length) if (!this._routes.length)
@ -332,10 +360,18 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
try { try {
await this._wrapApiCall(async () => { await this._wrapApiCall(async () => {
await this._browserType?._onWillCloseContext?.(this); await this._browserType?._onWillCloseContext?.(this);
if (this._options.recordHar) { for (const [harId, harParams] of this._harRecorders) {
const har = await this._channel.harExport(); const har = await this._channel.harExport({ harId });
const artifact = Artifact.from(har.artifact); const artifact = Artifact.from(har.artifact);
await artifact.saveAs(this._options.recordHar.path); // Server side will compress artifact if content is attach or if file is .zip.
const isCompressed = harParams.content === 'attach' || harParams.path.endsWith('.zip');
const needCompressed = harParams.path.endsWith('.zip');
if (isCompressed && !needCompressed) {
await artifact.saveAs(harParams.path + '.tmp');
await this._connection.localUtils()._channel.harUnzip({ zipFile: harParams.path + '.tmp', harFile: harParams.path });
} else {
await artifact.saveAs(harParams.path);
}
await artifact.delete(); await artifact.delete();
} }
}, true); }, true);
@ -387,6 +423,7 @@ function prepareRecordHarOptions(options: BrowserContextOptions['recordHar']): c
urlGlob: isString(options.urlFilter) ? options.urlFilter : undefined, urlGlob: isString(options.urlFilter) ? options.urlFilter : undefined,
urlRegexSource: isRegExp(options.urlFilter) ? options.urlFilter.source : undefined, urlRegexSource: isRegExp(options.urlFilter) ? options.urlFilter.source : undefined,
urlRegexFlags: isRegExp(options.urlFilter) ? options.urlFilter.flags : undefined, urlRegexFlags: isRegExp(options.urlFilter) ? options.urlFilter.flags : undefined,
mode: options.mode
}; };
} }

View file

@ -28,7 +28,6 @@ import type * as api from '../../types/types';
import { kBrowserClosedError } from '../common/errors'; import { kBrowserClosedError } from '../common/errors';
import { raceAgainstTimeout } from '../utils/timeoutRunner'; import { raceAgainstTimeout } from '../utils/timeoutRunner';
import type { Playwright } from './playwright'; import type { Playwright } from './playwright';
import { HarRouter } from './harRouter';
export interface BrowserServerLauncher { export interface BrowserServerLauncher {
launchServer(options?: LaunchServerOptions): Promise<api.BrowserServer>; launchServer(options?: LaunchServerOptions): Promise<api.BrowserServer>;
@ -95,7 +94,6 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
const logger = options.logger || this._defaultLaunchOptions?.logger; const logger = options.logger || this._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._defaultLaunchOptions, ...this._defaultContextOptions, ...options }; options = { ...this._defaultLaunchOptions, ...this._defaultContextOptions, ...options };
const harRouter = options.har ? await HarRouter.create(this._connection.localUtils(), options.har) : null;
const contextParams = await prepareBrowserContextParams(options); const contextParams = await prepareBrowserContextParams(options);
const persistentParams: channels.BrowserTypeLaunchPersistentContextParams = { const persistentParams: channels.BrowserTypeLaunchPersistentContextParams = {
...contextParams, ...contextParams,
@ -110,7 +108,6 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
context._options = contextParams; context._options = contextParams;
context._logger = logger; context._logger = logger;
context._setBrowserType(this); context._setBrowserType(this);
harRouter?.addRoute(context);
await this._onDidCreateContext?.(context); await this._onDidCreateContext?.(context);
return context; return context;
} }

View file

@ -26,9 +26,8 @@ import { zones } from '../utils/zones';
import type { ClientInstrumentation } from './clientInstrumentation'; 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 { JoiningEventEmitter } from './joiningEventEmitter';
export abstract class ChannelOwner<T extends channels.Channel = channels.Channel> extends JoiningEventEmitter { export abstract class ChannelOwner<T extends channels.Channel = channels.Channel> extends EventEmitter {
readonly _connection: Connection; readonly _connection: Connection;
private _parent: ChannelOwner | undefined; private _parent: ChannelOwner | undefined;
private _objects = new Map<string, ChannelOwner>(); private _objects = new Map<string, ChannelOwner>();

View file

@ -28,12 +28,10 @@ import { JSHandle, parseResult, serializeArgument } from './jsHandle';
import type { Page } from './page'; import type { Page } from './page';
import type { Env, WaitForEventOptions, Headers, BrowserContextOptions } from './types'; import type { Env, WaitForEventOptions, Headers, BrowserContextOptions } from './types';
import { Waiter } from './waiter'; import { Waiter } from './waiter';
import { HarRouter } from './harRouter';
type ElectronOptions = Omit<channels.ElectronLaunchOptions, 'env'|'extraHTTPHeaders'|'recordHar'> & { type ElectronOptions = Omit<channels.ElectronLaunchOptions, 'env'|'extraHTTPHeaders'|'recordHar'> & {
env?: Env, env?: Env,
extraHTTPHeaders?: Headers, extraHTTPHeaders?: Headers,
har?: BrowserContextOptions['har'],
recordHar?: BrowserContextOptions['recordHar'], recordHar?: BrowserContextOptions['recordHar'],
}; };
@ -53,10 +51,8 @@ export class Electron extends ChannelOwner<channels.ElectronChannel> implements
...await prepareBrowserContextParams(options), ...await prepareBrowserContextParams(options),
env: envObjectToArray(options.env ? options.env : process.env), env: envObjectToArray(options.env ? options.env : process.env),
}; };
const harRouter = options.har ? await HarRouter.create(this._connection.localUtils(), options.har) : null;
const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication); const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication);
app._context._options = params; app._context._options = params;
harRouter?.addRoute(app._context);
return app; return app;
} }
} }

View file

@ -19,32 +19,34 @@ import type { BrowserContext } from './browserContext';
import { Events } from './events'; import { Events } from './events';
import type { LocalUtils } from './localUtils'; import type { LocalUtils } from './localUtils';
import type { Route } from './network'; import type { Route } from './network';
import type { BrowserContextOptions } from './types'; import type { URLMatch } from './types';
import type { Page } from './page';
type HarOptions = NonNullable<BrowserContextOptions['har']>; type HarNotFoundAction = 'abort' | 'fallback';
export class HarRouter { export class HarRouter {
private _pattern: string | RegExp;
private _options: HarOptions | undefined;
private _localUtils: LocalUtils; private _localUtils: LocalUtils;
private _harId: string; private _harId: string;
private _notFoundAction: HarNotFoundAction;
private _options: { urlMatch?: URLMatch; baseURL?: string; };
static async create(localUtils: LocalUtils, options: HarOptions): Promise<HarRouter> { static async create(localUtils: LocalUtils, file: string, notFoundAction: HarNotFoundAction, options: { urlMatch?: URLMatch }): Promise<HarRouter> {
const { harId, error } = await localUtils._channel.harOpen({ file: options.path }); const { harId, error } = await localUtils._channel.harOpen({ file });
if (error) if (error)
throw new Error(error); throw new Error(error);
return new HarRouter(localUtils, harId!, options); return new HarRouter(localUtils, harId!, notFoundAction, options);
} }
constructor(localUtils: LocalUtils, harId: string, options?: HarOptions) { constructor(localUtils: LocalUtils, harId: string, notFoundAction: HarNotFoundAction, options: { urlMatch?: URLMatch }) {
this._localUtils = localUtils; this._localUtils = localUtils;
this._harId = harId; this._harId = harId;
this._pattern = options?.urlFilter ?? /.*/;
this._options = options; this._options = options;
this._notFoundAction = notFoundAction;
} }
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._channel.harLookup({
harId: this._harId, harId: this._harId,
url: request.url(), url: request.url(),
@ -56,7 +58,7 @@ export class HarRouter {
if (response.action === 'redirect') { if (response.action === 'redirect') {
debugLogger.log('api', `HAR: ${route.request().url()} redirected to ${response.redirectURL}`); debugLogger.log('api', `HAR: ${route.request().url()} redirected to ${response.redirectURL}`);
await route._abort(undefined, response.redirectURL); await route._redirectNavigationRequest(response.redirectURL!);
return; return;
} }
@ -73,20 +75,24 @@ export class HarRouter {
debugLogger.log('api', 'HAR: ' + response.message!); debugLogger.log('api', 'HAR: ' + response.message!);
// Report the error, but fall through to the default handler. // Report the error, but fall through to the default handler.
if (this._options?.fallback === 'continue') { if (this._notFoundAction === 'abort') {
await route.fallback(); await route.abort();
return; return;
} }
debugLogger.log('api', `HAR: ${route.request().method()} ${route.request().url()} aborted - no such entry in HAR file`); await route.fallback();
await route.abort();
} }
async addRoute(context: BrowserContext) { async addContextRoute(context: BrowserContext) {
await context.route(this._pattern, route => this._handle(route)); await context.route(this._options.urlMatch || '**/*', route => this._handle(route));
context.once(Events.BrowserContext.Close, () => this.dispose()); context.once(Events.BrowserContext.Close, () => this.dispose());
} }
async addPageRoute(page: Page) {
await page.route(this._options.urlMatch || '**/*', route => this._handle(route));
page.once(Events.Page.Close, () => this.dispose());
}
dispose() { dispose() {
this._localUtils._channel.harClose({ harId: this._harId }).catch(() => {}); this._localUtils._channel.harClose({ harId: this._harId }).catch(() => {});
} }

View file

@ -54,7 +54,7 @@ export class JoiningEventEmitter implements EventEmitter {
} }
removeAllListeners(event?: string | symbol | undefined): this { removeAllListeners(event?: string | symbol | undefined): this {
this._emitterDelegate.removeAllListeners(); this._emitterDelegate.removeAllListeners(event);
return this; return this;
} }
@ -119,7 +119,8 @@ export class JoiningEventEmitter implements EventEmitter {
} }
private _wrapper(listener: (...args: any[]) => void) { private _wrapper(listener: (...args: any[]) => void) {
return (listener as any)[wrapperListener]; // Fallback to original listener if not wrapped to ensure backwards compatibility Node.js's event emitter
return (listener as any)[wrapperListener] ?? listener;
} }
private _original(wrapper: Function): Function { private _original(wrapper: Function): Function {

View file

@ -282,12 +282,14 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
} }
async abort(errorCode?: string) { async abort(errorCode?: string) {
await this._abort(errorCode); this._checkNotHandled();
await this._raceWithPageClose(this._channel.abort({ errorCode }));
this._reportHandled(true);
} }
async _abort(errorCode?: string, redirectAbortedNavigationToUrl?: string) { async _redirectNavigationRequest(url: string) {
this._checkNotHandled(); this._checkNotHandled();
await this._raceWithPageClose(this._channel.abort({ errorCode, redirectAbortedNavigationToUrl })); await this._raceWithPageClose(this._channel.redirectNavigationRequest({ url }));
this._reportHandled(true); this._reportHandled(true);
} }

View file

@ -43,6 +43,7 @@ import type { APIRequestContext } from './fetch';
import { FileChooser } from './fileChooser'; import { FileChooser } from './fileChooser';
import type { WaitForNavigationOptions } from './frame'; import type { WaitForNavigationOptions } from './frame';
import { Frame, verifyLoadState } from './frame'; import { Frame, verifyLoadState } from './frame';
import { HarRouter } from './harRouter';
import { Keyboard, Mouse, Touchscreen } from './input'; import { Keyboard, Mouse, Touchscreen } from './input';
import { assertMaxArguments, JSHandle, parseResult, serializeArgument } from './jsHandle'; import { assertMaxArguments, JSHandle, parseResult, serializeArgument } from './jsHandle';
import type { FrameLocator, Locator, LocatorOptions } from './locator'; import type { FrameLocator, Locator, LocatorOptions } from './locator';
@ -178,8 +179,10 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
} }
private async _onRoute(route: Route, request: Request) { private async _onRoute(route: Route, request: Request) {
const routeHandlers = this._routes.filter(r => r.matches(request.url())); const routeHandlers = this._routes.slice();
for (const routeHandler of routeHandlers) { for (const routeHandler of routeHandlers) {
if (!routeHandler.matches(request.url()))
continue;
if (routeHandler.willExpire()) if (routeHandler.willExpire())
this._routes.splice(this._routes.indexOf(routeHandler), 1); this._routes.splice(this._routes.indexOf(routeHandler), 1);
const handled = await routeHandler.handle(route, request); const handled = await routeHandler.handle(route, request);
@ -465,6 +468,15 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
await this._channel.setNetworkInterceptionEnabled({ enabled: true }); await this._channel.setNetworkInterceptionEnabled({ enabled: true });
} }
async routeFromHAR(har: string, options: { url?: string | RegExp, notFound?: 'abort' | 'fallback', update?: boolean } = {}): Promise<void> {
if (options.update) {
await this._browserContext._recordIntoHAR(har, this, options);
return;
}
const harRouter = await HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', { urlMatch: options.url });
harRouter.addPageRoute(this);
}
async unroute(url: URLMatch, handler?: RouteHandlerCallback): Promise<void> { async unroute(url: URLMatch, handler?: RouteHandlerCallback): Promise<void> {
this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler)); this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler));
if (!this._routes.length) if (!this._routes.length)

View file

@ -63,6 +63,7 @@ export type BrowserContextOptions = Omit<channels.BrowserNewContextOptions, 'vie
path: string, path: string,
omitContent?: boolean, omitContent?: boolean,
content?: 'omit' | 'embed' | 'attach', content?: 'omit' | 'embed' | 'attach',
mode?: 'full' | 'minimal',
urlFilter?: string | RegExp, urlFilter?: string | RegExp,
}, },
}; };

View file

@ -266,6 +266,7 @@ export type SerializedError = {
export type RecordHarOptions = { export type RecordHarOptions = {
path: string, path: string,
content?: 'embed' | 'attach' | 'omit', content?: 'embed' | 'attach' | 'omit',
mode?: 'full' | 'minimal',
urlGlob?: string, urlGlob?: string,
urlRegexSource?: string, urlRegexSource?: string,
urlRegexFlags?: string, urlRegexFlags?: string,
@ -381,6 +382,7 @@ export interface LocalUtilsChannel extends LocalUtilsEventTarget, Channel {
harOpen(params: LocalUtilsHarOpenParams, metadata?: Metadata): Promise<LocalUtilsHarOpenResult>; harOpen(params: LocalUtilsHarOpenParams, metadata?: Metadata): Promise<LocalUtilsHarOpenResult>;
harLookup(params: LocalUtilsHarLookupParams, metadata?: Metadata): Promise<LocalUtilsHarLookupResult>; harLookup(params: LocalUtilsHarLookupParams, metadata?: Metadata): Promise<LocalUtilsHarLookupResult>;
harClose(params: LocalUtilsHarCloseParams, metadata?: Metadata): Promise<LocalUtilsHarCloseResult>; harClose(params: LocalUtilsHarCloseParams, metadata?: Metadata): Promise<LocalUtilsHarCloseResult>;
harUnzip(params: LocalUtilsHarUnzipParams, metadata?: Metadata): Promise<LocalUtilsHarUnzipResult>;
} }
export type LocalUtilsZipParams = { export type LocalUtilsZipParams = {
zipFile: string, zipFile: string,
@ -426,6 +428,14 @@ export type LocalUtilsHarCloseOptions = {
}; };
export type LocalUtilsHarCloseResult = void; export type LocalUtilsHarCloseResult = void;
export type LocalUtilsHarUnzipParams = {
zipFile: string,
harFile: string,
};
export type LocalUtilsHarUnzipOptions = {
};
export type LocalUtilsHarUnzipResult = void;
export interface LocalUtilsEvents { export interface LocalUtilsEvents {
} }
@ -1118,7 +1128,8 @@ export interface BrowserContextChannel extends BrowserContextEventTarget, EventT
pause(params?: BrowserContextPauseParams, metadata?: Metadata): Promise<BrowserContextPauseResult>; pause(params?: BrowserContextPauseParams, metadata?: Metadata): Promise<BrowserContextPauseResult>;
recorderSupplementEnable(params: BrowserContextRecorderSupplementEnableParams, metadata?: Metadata): Promise<BrowserContextRecorderSupplementEnableResult>; recorderSupplementEnable(params: BrowserContextRecorderSupplementEnableParams, metadata?: Metadata): Promise<BrowserContextRecorderSupplementEnableResult>;
newCDPSession(params: BrowserContextNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextNewCDPSessionResult>; newCDPSession(params: BrowserContextNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextNewCDPSessionResult>;
harExport(params?: BrowserContextHarExportParams, metadata?: Metadata): Promise<BrowserContextHarExportResult>; harStart(params: BrowserContextHarStartParams, metadata?: Metadata): Promise<BrowserContextHarStartResult>;
harExport(params: BrowserContextHarExportParams, metadata?: Metadata): Promise<BrowserContextHarExportResult>;
createTempFile(params: BrowserContextCreateTempFileParams, metadata?: Metadata): Promise<BrowserContextCreateTempFileResult>; createTempFile(params: BrowserContextCreateTempFileParams, metadata?: Metadata): Promise<BrowserContextCreateTempFileResult>;
} }
export type BrowserContextBindingCallEvent = { export type BrowserContextBindingCallEvent = {
@ -1324,8 +1335,22 @@ export type BrowserContextNewCDPSessionOptions = {
export type BrowserContextNewCDPSessionResult = { export type BrowserContextNewCDPSessionResult = {
session: CDPSessionChannel, session: CDPSessionChannel,
}; };
export type BrowserContextHarExportParams = {}; export type BrowserContextHarStartParams = {
export type BrowserContextHarExportOptions = {}; page?: PageChannel,
options: RecordHarOptions,
};
export type BrowserContextHarStartOptions = {
page?: PageChannel,
};
export type BrowserContextHarStartResult = {
harId: string,
};
export type BrowserContextHarExportParams = {
harId?: string,
};
export type BrowserContextHarExportOptions = {
harId?: string,
};
export type BrowserContextHarExportResult = { export type BrowserContextHarExportResult = {
artifact: ArtifactChannel, artifact: ArtifactChannel,
}; };
@ -3158,17 +3183,23 @@ export interface RouteEventTarget {
} }
export interface RouteChannel extends RouteEventTarget, Channel { export interface RouteChannel extends RouteEventTarget, Channel {
_type_Route: boolean; _type_Route: boolean;
redirectNavigationRequest(params: RouteRedirectNavigationRequestParams, metadata?: Metadata): Promise<RouteRedirectNavigationRequestResult>;
abort(params: RouteAbortParams, metadata?: Metadata): Promise<RouteAbortResult>; abort(params: RouteAbortParams, metadata?: Metadata): Promise<RouteAbortResult>;
continue(params: RouteContinueParams, metadata?: Metadata): Promise<RouteContinueResult>; continue(params: RouteContinueParams, metadata?: Metadata): Promise<RouteContinueResult>;
fulfill(params: RouteFulfillParams, metadata?: Metadata): Promise<RouteFulfillResult>; fulfill(params: RouteFulfillParams, metadata?: Metadata): Promise<RouteFulfillResult>;
} }
export type RouteRedirectNavigationRequestParams = {
url: string,
};
export type RouteRedirectNavigationRequestOptions = {
};
export type RouteRedirectNavigationRequestResult = void;
export type RouteAbortParams = { export type RouteAbortParams = {
errorCode?: string, errorCode?: string,
redirectAbortedNavigationToUrl?: string,
}; };
export type RouteAbortOptions = { export type RouteAbortOptions = {
errorCode?: string, errorCode?: string,
redirectAbortedNavigationToUrl?: string,
}; };
export type RouteAbortResult = void; export type RouteAbortResult = void;
export type RouteContinueParams = { export type RouteContinueParams = {

View file

@ -231,6 +231,11 @@ RecordHarOptions:
- embed - embed
- attach - attach
- omit - omit
mode:
type: enum?
literals:
- full
- minimal
urlGlob: string? urlGlob: string?
urlRegexSource: string? urlRegexSource: string?
urlRegexFlags: string? urlRegexFlags: string?
@ -515,6 +520,11 @@ LocalUtils:
parameters: parameters:
harId: string harId: string
harUnzip:
parameters:
zipFile: string
harFile: string
Root: Root:
type: interface type: interface
@ -921,7 +931,16 @@ BrowserContext:
returns: returns:
session: CDPSession session: CDPSession
harStart:
parameters:
page: Page?
options: RecordHarOptions
returns:
harId: string
harExport: harExport:
parameters:
harId: string?
returns: returns:
artifact: Artifact artifact: Artifact
@ -2491,10 +2510,13 @@ Route:
commands: commands:
redirectNavigationRequest:
parameters:
url: string
abort: abort:
parameters: parameters:
errorCode: string? errorCode: string?
redirectAbortedNavigationToUrl: string?
continue: continue:
parameters: parameters:

View file

@ -156,6 +156,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
scheme.RecordHarOptions = tObject({ scheme.RecordHarOptions = tObject({
path: tString, path: tString,
content: tOptional(tEnum(['embed', 'attach', 'omit'])), content: tOptional(tEnum(['embed', 'attach', 'omit'])),
mode: tOptional(tEnum(['full', 'minimal'])),
urlGlob: tOptional(tString), urlGlob: tOptional(tString),
urlRegexSource: tOptional(tString), urlRegexSource: tOptional(tString),
urlRegexFlags: tOptional(tString), urlRegexFlags: tOptional(tString),
@ -219,6 +220,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
scheme.LocalUtilsHarCloseParams = tObject({ scheme.LocalUtilsHarCloseParams = tObject({
harId: tString, harId: tString,
}); });
scheme.LocalUtilsHarUnzipParams = tObject({
zipFile: tString,
harFile: tString,
});
scheme.RootInitializeParams = tObject({ scheme.RootInitializeParams = tObject({
sdkLanguage: tString, sdkLanguage: tString,
}); });
@ -526,7 +531,13 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
page: tOptional(tChannel('Page')), page: tOptional(tChannel('Page')),
frame: tOptional(tChannel('Frame')), frame: tOptional(tChannel('Frame')),
}); });
scheme.BrowserContextHarExportParams = tOptional(tObject({})); scheme.BrowserContextHarStartParams = tObject({
page: tOptional(tChannel('Page')),
options: tType('RecordHarOptions'),
});
scheme.BrowserContextHarExportParams = tObject({
harId: tOptional(tString),
});
scheme.BrowserContextCreateTempFileParams = tObject({ scheme.BrowserContextCreateTempFileParams = tObject({
name: tString, name: tString,
}); });
@ -1181,9 +1192,11 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
}); });
scheme.RequestResponseParams = tOptional(tObject({})); scheme.RequestResponseParams = tOptional(tObject({}));
scheme.RequestRawRequestHeadersParams = tOptional(tObject({})); scheme.RequestRawRequestHeadersParams = tOptional(tObject({}));
scheme.RouteRedirectNavigationRequestParams = tObject({
url: tString,
});
scheme.RouteAbortParams = tObject({ scheme.RouteAbortParams = tObject({
errorCode: tOptional(tString), errorCode: tOptional(tString),
redirectAbortedNavigationToUrl: tOptional(tString),
}); });
scheme.RouteContinueParams = tObject({ scheme.RouteContinueParams = tObject({
url: tOptional(tString), url: tOptional(tString),

View file

@ -17,7 +17,7 @@
import * as os from 'os'; import * as os from 'os';
import { TimeoutSettings } from '../common/timeoutSettings'; import { TimeoutSettings } from '../common/timeoutSettings';
import { debugMode } from '../utils'; import { createGuid, debugMode } from '../utils';
import { mkdirIfNeeded } from '../utils/fileUtils'; import { mkdirIfNeeded } from '../utils/fileUtils';
import type { Browser, BrowserOptions } from './browser'; import type { Browser, BrowserOptions } from './browser';
import type { Download } from './download'; import type { Download } from './download';
@ -40,6 +40,7 @@ import { HarRecorder } from './har/harRecorder';
import { Recorder } from './recorder'; import { Recorder } from './recorder';
import * as consoleApiSource from '../generated/consoleApiSource'; import * as consoleApiSource from '../generated/consoleApiSource';
import { BrowserContextAPIRequestContext } from './fetch'; import { BrowserContextAPIRequestContext } from './fetch';
import type { Artifact } from './artifact';
export abstract class BrowserContext extends SdkObject { export abstract class BrowserContext extends SdkObject {
static Events = { static Events = {
@ -67,7 +68,7 @@ export abstract class BrowserContext extends SdkObject {
readonly _browserContextId: string | undefined; readonly _browserContextId: string | undefined;
private _selectors?: Selectors; private _selectors?: Selectors;
private _origins = new Set<string>(); private _origins = new Set<string>();
readonly _harRecorder: HarRecorder | undefined; readonly _harRecorders = new Map<string, HarRecorder>();
readonly tracing: Tracing; readonly tracing: Tracing;
readonly fetchRequest: BrowserContextAPIRequestContext; readonly fetchRequest: BrowserContextAPIRequestContext;
private _customCloseHandler?: () => Promise<any>; private _customCloseHandler?: () => Promise<any>;
@ -87,7 +88,7 @@ export abstract class BrowserContext extends SdkObject {
this.fetchRequest = new BrowserContextAPIRequestContext(this); this.fetchRequest = new BrowserContextAPIRequestContext(this);
if (this._options.recordHar) if (this._options.recordHar)
this._harRecorder = new HarRecorder(this, this._options.recordHar); this._harRecorders.set('', new HarRecorder(this, null, this._options.recordHar));
this.tracing = new Tracing(this, browser.options.tracesDir); this.tracing = new Tracing(this, browser.options.tracesDir);
} }
@ -316,7 +317,8 @@ export abstract class BrowserContext extends SdkObject {
this.emit(BrowserContext.Events.BeforeClose); this.emit(BrowserContext.Events.BeforeClose);
this._closedStatus = 'closing'; this._closedStatus = 'closing';
await this._harRecorder?.flush(); for (const harRecorder of this._harRecorders.values())
await harRecorder.flush();
await this.tracing.flush(); await this.tracing.flush();
// Cleanup. // Cleanup.
@ -442,6 +444,17 @@ export abstract class BrowserContext extends SdkObject {
this.on(BrowserContext.Events.Page, installInPage); this.on(BrowserContext.Events.Page, installInPage);
return Promise.all(this.pages().map(installInPage)); return Promise.all(this.pages().map(installInPage));
} }
async _harStart(page: Page | null, options: channels.RecordHarOptions): Promise<string> {
const harId = createGuid();
this._harRecorders.set(harId, new HarRecorder(this, page, options));
return harId;
}
async _harExport(harId: string | undefined): Promise<Artifact> {
const recorder = this._harRecorders.get(harId || '')!;
return recorder.export();
}
} }
export function assertBrowserContextIsNotOwned(context: BrowserContext) { export function assertBrowserContextIsNotOwned(context: BrowserContext) {

View file

@ -810,7 +810,7 @@ CORS RFC1918 enforcement.
export type AttributionReportingIssueType = "PermissionPolicyDisabled"|"AttributionSourceUntrustworthyOrigin"|"AttributionUntrustworthyOrigin"|"InvalidHeader"; export type AttributionReportingIssueType = "PermissionPolicyDisabled"|"AttributionSourceUntrustworthyOrigin"|"AttributionUntrustworthyOrigin"|"InvalidHeader";
/** /**
* Details for issues around "Attribution Reporting API" usage. * Details for issues around "Attribution Reporting API" usage.
Explainer: https://github.com/WICG/conversion-measurement-api Explainer: https://github.com/WICG/attribution-reporting-api
*/ */
export interface AttributionReportingIssueDetails { export interface AttributionReportingIssueDetails {
violationType: AttributionReportingIssueType; violationType: AttributionReportingIssueType;
@ -849,7 +849,7 @@ instead of "limited-quirks".
errorType: GenericIssueErrorType; errorType: GenericIssueErrorType;
frameId?: Page.FrameId; frameId?: Page.FrameId;
} }
export type DeprecationIssueType = "AuthorizationCoveredByWildcard"|"CanRequestURLHTTPContainingNewline"|"ChromeLoadTimesConnectionInfo"|"ChromeLoadTimesFirstPaintAfterLoadTime"|"ChromeLoadTimesWasAlternateProtocolAvailable"|"CookieWithTruncatingChar"|"CrossOriginAccessBasedOnDocumentDomain"|"CrossOriginWindowAlert"|"CrossOriginWindowConfirm"|"CSSSelectorInternalMediaControlsOverlayCastButton"|"CustomCursorIntersectsViewport"|"DeprecationExample"|"DocumentDomainSettingWithoutOriginAgentClusterHeader"|"EventPath"|"GeolocationInsecureOrigin"|"GeolocationInsecureOriginDeprecatedNotRemoved"|"GetUserMediaInsecureOrigin"|"HostCandidateAttributeGetter"|"InsecurePrivateNetworkSubresourceRequest"|"LegacyConstraintGoogIPv6"|"LocalCSSFileExtensionRejected"|"MediaElementAudioSourceNode"|"MediaSourceAbortRemove"|"MediaSourceDurationTruncatingBuffered"|"NoSysexWebMIDIWithoutPermission"|"NotificationInsecureOrigin"|"NotificationPermissionRequestedIframe"|"ObsoleteWebRtcCipherSuite"|"PaymentRequestBasicCard"|"PaymentRequestShowWithoutGesture"|"PictureSourceSrc"|"PrefixedCancelAnimationFrame"|"PrefixedRequestAnimationFrame"|"PrefixedStorageInfo"|"PrefixedVideoDisplayingFullscreen"|"PrefixedVideoEnterFullscreen"|"PrefixedVideoEnterFullScreen"|"PrefixedVideoExitFullscreen"|"PrefixedVideoExitFullScreen"|"PrefixedVideoSupportsFullscreen"|"RangeExpand"|"RequestedSubresourceWithEmbeddedCredentials"|"RTCConstraintEnableDtlsSrtpFalse"|"RTCConstraintEnableDtlsSrtpTrue"|"RTCPeerConnectionComplexPlanBSdpUsingDefaultSdpSemantics"|"RTCPeerConnectionSdpSemanticsPlanB"|"RtcpMuxPolicyNegotiate"|"RTPDataChannel"|"SharedArrayBufferConstructedWithoutIsolation"|"TextToSpeech_DisallowedByAutoplay"|"V8SharedArrayBufferConstructedInExtensionWithoutIsolation"|"XHRJSONEncodingDetection"|"XMLHttpRequestSynchronousInNonWorkerOutsideBeforeUnload"|"XRSupportsSession"; export type DeprecationIssueType = "AuthorizationCoveredByWildcard"|"CanRequestURLHTTPContainingNewline"|"ChromeLoadTimesConnectionInfo"|"ChromeLoadTimesFirstPaintAfterLoadTime"|"ChromeLoadTimesWasAlternateProtocolAvailable"|"CookieWithTruncatingChar"|"CrossOriginAccessBasedOnDocumentDomain"|"CrossOriginWindowAlert"|"CrossOriginWindowConfirm"|"CSSSelectorInternalMediaControlsOverlayCastButton"|"DeprecationExample"|"DocumentDomainSettingWithoutOriginAgentClusterHeader"|"EventPath"|"GeolocationInsecureOrigin"|"GeolocationInsecureOriginDeprecatedNotRemoved"|"GetUserMediaInsecureOrigin"|"HostCandidateAttributeGetter"|"IdentityInCanMakePaymentEvent"|"InsecurePrivateNetworkSubresourceRequest"|"LegacyConstraintGoogIPv6"|"LocalCSSFileExtensionRejected"|"MediaSourceAbortRemove"|"MediaSourceDurationTruncatingBuffered"|"NoSysexWebMIDIWithoutPermission"|"NotificationInsecureOrigin"|"NotificationPermissionRequestedIframe"|"ObsoleteWebRtcCipherSuite"|"OpenWebDatabaseInsecureContext"|"PictureSourceSrc"|"PrefixedCancelAnimationFrame"|"PrefixedRequestAnimationFrame"|"PrefixedStorageInfo"|"PrefixedVideoDisplayingFullscreen"|"PrefixedVideoEnterFullscreen"|"PrefixedVideoEnterFullScreen"|"PrefixedVideoExitFullscreen"|"PrefixedVideoExitFullScreen"|"PrefixedVideoSupportsFullscreen"|"RangeExpand"|"RequestedSubresourceWithEmbeddedCredentials"|"RTCConstraintEnableDtlsSrtpFalse"|"RTCConstraintEnableDtlsSrtpTrue"|"RTCPeerConnectionComplexPlanBSdpUsingDefaultSdpSemantics"|"RTCPeerConnectionSdpSemanticsPlanB"|"RtcpMuxPolicyNegotiate"|"SharedArrayBufferConstructedWithoutIsolation"|"TextToSpeech_DisallowedByAutoplay"|"V8SharedArrayBufferConstructedInExtensionWithoutIsolation"|"XHRJSONEncodingDetection"|"XMLHttpRequestSynchronousInNonWorkerOutsideBeforeUnload"|"XRSupportsSession";
/** /**
* This issue tracks information needed to print a deprecation message. * This issue tracks information needed to print a deprecation message.
https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/third_party/blink/renderer/core/frame/deprecation/README.md https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/third_party/blink/renderer/core/frame/deprecation/README.md
@ -1532,6 +1532,10 @@ inspector" rules), "regular" for regular stylesheets.
* Pseudo element type. * Pseudo element type.
*/ */
pseudoType: DOM.PseudoType; pseudoType: DOM.PseudoType;
/**
* Pseudo element custom ident.
*/
pseudoIdentifier?: string;
/** /**
* Matches of CSS rules applicable to the pseudo style. * Matches of CSS rules applicable to the pseudo style.
*/ */
@ -2068,6 +2072,10 @@ and additional information such as platformFontFamily and fontVariationAxes.
* The font-stretch. * The font-stretch.
*/ */
fontStretch: string; fontStretch: string;
/**
* The font-display.
*/
fontDisplay: string;
/** /**
* The unicode-range. * The unicode-range.
*/ */
@ -2958,6 +2966,11 @@ fire DOM events for nodes known to the client.
* Pseudo element type for this node. * Pseudo element type for this node.
*/ */
pseudoType?: PseudoType; pseudoType?: PseudoType;
/**
* Pseudo element identifier for this node. Only present if there is a
valid pseudoType.
*/
pseudoIdentifier?: string;
/** /**
* Shadow root type. * Shadow root type.
*/ */
@ -2997,6 +3010,7 @@ The property is always undefined now.
*/ */
isSVG?: boolean; isSVG?: boolean;
compatibilityMode?: CompatibilityMode; compatibilityMode?: CompatibilityMode;
assignedSlot?: BackendNode;
} }
/** /**
* A structure holding an RGBA color. * A structure holding an RGBA color.
@ -4706,6 +4720,11 @@ getSnapshot was true.
* Type of a pseudo element node. * Type of a pseudo element node.
*/ */
pseudoType?: RareStringData; pseudoType?: RareStringData;
/**
* Pseudo element identifier for this node. Only present if there is a
valid pseudoType.
*/
pseudoIdentifier?: RareStringData;
/** /**
* Whether this DOM node responds to mouse clicks. This includes nodes that have had click * Whether this DOM node responds to mouse clicks. This includes nodes that have had click
event listeners attached via JavaScript as well as anchor tags that naturally navigate when event listeners attached via JavaScript as well as anchor tags that naturally navigate when
@ -4978,12 +4997,6 @@ The final text color opacity is computed based on the opacity of all overlapping
} }
export type setDOMStorageItemReturnValue = { export type setDOMStorageItemReturnValue = {
} }
export type getStorageKeyForFrameParameters = {
frameId: Page.FrameId;
}
export type getStorageKeyForFrameReturnValue = {
storageKey: SerializedStorageKey;
}
} }
export module Database { export module Database {
@ -5533,6 +5546,14 @@ on Android.
} }
export type setDisabledImageTypesReturnValue = { export type setDisabledImageTypesReturnValue = {
} }
export type setHardwareConcurrencyOverrideParameters = {
/**
* Hardware concurrency to report
*/
hardwareConcurrency: number;
}
export type setHardwareConcurrencyOverrideReturnValue = {
}
/** /**
* Allows overriding user agent with the given string. * Allows overriding user agent with the given string.
*/ */
@ -8063,7 +8084,7 @@ the same request (but not for redirected requests).
initiatorIPAddressSpace: IPAddressSpace; initiatorIPAddressSpace: IPAddressSpace;
privateNetworkRequestPolicy: PrivateNetworkRequestPolicy; privateNetworkRequestPolicy: PrivateNetworkRequestPolicy;
} }
export type CrossOriginOpenerPolicyValue = "SameOrigin"|"SameOriginAllowPopups"|"UnsafeNone"|"SameOriginPlusCoep"|"SameOriginAllowPopupsPlusCoep"; export type CrossOriginOpenerPolicyValue = "SameOrigin"|"SameOriginAllowPopups"|"RestrictProperties"|"UnsafeNone"|"SameOriginPlusCoep"|"RestrictPropertiesPlusCoep";
export interface CrossOriginOpenerPolicyStatus { export interface CrossOriginOpenerPolicyStatus {
value: CrossOriginOpenerPolicyValue; value: CrossOriginOpenerPolicyValue;
reportOnlyValue: CrossOriginOpenerPolicyValue; reportOnlyValue: CrossOriginOpenerPolicyValue;
@ -10166,6 +10187,21 @@ Backend then generates 'inspectNodeRequested' event upon element selection.
adFrameType: AdFrameType; adFrameType: AdFrameType;
explanations?: AdFrameExplanation[]; explanations?: AdFrameExplanation[];
} }
/**
* Identifies the bottom-most script which caused the frame to be labelled
as an ad.
*/
export interface AdScriptId {
/**
* Script Id of the bottom-most script which caused the frame to be labelled
as an ad.
*/
scriptId: Runtime.ScriptId;
/**
* Id of adScriptId's debugger.
*/
debuggerId: Runtime.UniqueDebuggerId;
}
/** /**
* Indicates whether the frame is a secure context and why it is the case. * Indicates whether the frame is a secure context and why it is the case.
*/ */
@ -10179,7 +10215,7 @@ Backend then generates 'inspectNodeRequested' event upon element selection.
* 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"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"browsing-topics"|"camera"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-full"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-reduced"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"cross-origin-isolated"|"direct-sockets"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"storage-access-api"|"sync-xhr"|"trust-token-redemption"|"usb"|"vertical-scroll"|"web-share"|"window-placement"|"xr-spatial-tracking"; export type PermissionsPolicyFeature = "accelerometer"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-full"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-reduced"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"cross-origin-isolated"|"direct-sockets"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"storage-access-api"|"sync-xhr"|"trust-token-redemption"|"usb"|"vertical-scroll"|"web-share"|"window-placement"|"xr-spatial-tracking";
/** /**
* Reason for a permissions policy feature to be disabled. * Reason for a permissions policy feature to be disabled.
*/ */
@ -10644,7 +10680,7 @@ Example URLs: http://www.google.com/file.html -> "google.com"
/** /**
* List of not restored reasons for back-forward cache. * List of not restored reasons for back-forward cache.
*/ */
export type BackForwardCacheNotRestoredReason = "NotPrimaryMainFrame"|"BackForwardCacheDisabled"|"RelatedActiveContentsExist"|"HTTPStatusNotOK"|"SchemeNotHTTPOrHTTPS"|"Loading"|"WasGrantedMediaAccess"|"DisableForRenderFrameHostCalled"|"DomainNotAllowed"|"HTTPMethodNotGET"|"SubframeIsNavigating"|"Timeout"|"CacheLimit"|"JavaScriptExecution"|"RendererProcessKilled"|"RendererProcessCrashed"|"SchedulerTrackedFeatureUsed"|"ConflictingBrowsingInstance"|"CacheFlushed"|"ServiceWorkerVersionActivation"|"SessionRestored"|"ServiceWorkerPostMessage"|"EnteredBackForwardCacheBeforeServiceWorkerHostAdded"|"RenderFrameHostReused_SameSite"|"RenderFrameHostReused_CrossSite"|"ServiceWorkerClaim"|"IgnoreEventAndEvict"|"HaveInnerContents"|"TimeoutPuttingInCache"|"BackForwardCacheDisabledByLowMemory"|"BackForwardCacheDisabledByCommandLine"|"NetworkRequestDatapipeDrainedAsBytesConsumer"|"NetworkRequestRedirected"|"NetworkRequestTimeout"|"NetworkExceedsBufferLimit"|"NavigationCancelledWhileRestoring"|"NotMostRecentNavigationEntry"|"BackForwardCacheDisabledForPrerender"|"UserAgentOverrideDiffers"|"ForegroundCacheLimit"|"BrowsingInstanceNotSwapped"|"BackForwardCacheDisabledForDelegate"|"UnloadHandlerExistsInMainFrame"|"UnloadHandlerExistsInSubFrame"|"ServiceWorkerUnregistration"|"CacheControlNoStore"|"CacheControlNoStoreCookieModified"|"CacheControlNoStoreHTTPOnlyCookieModified"|"NoResponseHead"|"Unknown"|"ActivationNavigationsDisallowedForBug1234857"|"ErrorDocument"|"FencedFramesEmbedder"|"WebSocket"|"WebTransport"|"WebRTC"|"MainResourceHasCacheControlNoStore"|"MainResourceHasCacheControlNoCache"|"SubresourceHasCacheControlNoStore"|"SubresourceHasCacheControlNoCache"|"ContainsPlugins"|"DocumentLoaded"|"DedicatedWorkerOrWorklet"|"OutstandingNetworkRequestOthers"|"OutstandingIndexedDBTransaction"|"RequestedNotificationsPermission"|"RequestedMIDIPermission"|"RequestedAudioCapturePermission"|"RequestedVideoCapturePermission"|"RequestedBackForwardCacheBlockedSensors"|"RequestedBackgroundWorkPermission"|"BroadcastChannel"|"IndexedDBConnection"|"WebXR"|"SharedWorker"|"WebLocks"|"WebHID"|"WebShare"|"RequestedStorageAccessGrant"|"WebNfc"|"OutstandingNetworkRequestFetch"|"OutstandingNetworkRequestXHR"|"AppBanner"|"Printing"|"WebDatabase"|"PictureInPicture"|"Portal"|"SpeechRecognizer"|"IdleManager"|"PaymentManager"|"SpeechSynthesis"|"KeyboardLock"|"WebOTPService"|"OutstandingNetworkRequestDirectSocket"|"InjectedJavascript"|"InjectedStyleSheet"|"Dummy"|"ContentSecurityHandler"|"ContentWebAuthenticationAPI"|"ContentFileChooser"|"ContentSerial"|"ContentFileSystemAccess"|"ContentMediaDevicesDispatcherHost"|"ContentWebBluetooth"|"ContentWebUSB"|"ContentMediaSession"|"ContentMediaSessionService"|"ContentScreenReader"|"EmbedderPopupBlockerTabHelper"|"EmbedderSafeBrowsingTriggeredPopupBlocker"|"EmbedderSafeBrowsingThreatDetails"|"EmbedderAppBannerManager"|"EmbedderDomDistillerViewerSource"|"EmbedderDomDistillerSelfDeletingRequestDelegate"|"EmbedderOomInterventionTabHelper"|"EmbedderOfflinePage"|"EmbedderChromePasswordManagerClientBindCredentialManager"|"EmbedderPermissionRequestManager"|"EmbedderModalDialog"|"EmbedderExtensions"|"EmbedderExtensionMessaging"|"EmbedderExtensionMessagingForOpenPort"|"EmbedderExtensionSentMessageToCachedFrame"; export type BackForwardCacheNotRestoredReason = "NotPrimaryMainFrame"|"BackForwardCacheDisabled"|"RelatedActiveContentsExist"|"HTTPStatusNotOK"|"SchemeNotHTTPOrHTTPS"|"Loading"|"WasGrantedMediaAccess"|"DisableForRenderFrameHostCalled"|"DomainNotAllowed"|"HTTPMethodNotGET"|"SubframeIsNavigating"|"Timeout"|"CacheLimit"|"JavaScriptExecution"|"RendererProcessKilled"|"RendererProcessCrashed"|"SchedulerTrackedFeatureUsed"|"ConflictingBrowsingInstance"|"CacheFlushed"|"ServiceWorkerVersionActivation"|"SessionRestored"|"ServiceWorkerPostMessage"|"EnteredBackForwardCacheBeforeServiceWorkerHostAdded"|"RenderFrameHostReused_SameSite"|"RenderFrameHostReused_CrossSite"|"ServiceWorkerClaim"|"IgnoreEventAndEvict"|"HaveInnerContents"|"TimeoutPuttingInCache"|"BackForwardCacheDisabledByLowMemory"|"BackForwardCacheDisabledByCommandLine"|"NetworkRequestDatapipeDrainedAsBytesConsumer"|"NetworkRequestRedirected"|"NetworkRequestTimeout"|"NetworkExceedsBufferLimit"|"NavigationCancelledWhileRestoring"|"NotMostRecentNavigationEntry"|"BackForwardCacheDisabledForPrerender"|"UserAgentOverrideDiffers"|"ForegroundCacheLimit"|"BrowsingInstanceNotSwapped"|"BackForwardCacheDisabledForDelegate"|"UnloadHandlerExistsInMainFrame"|"UnloadHandlerExistsInSubFrame"|"ServiceWorkerUnregistration"|"CacheControlNoStore"|"CacheControlNoStoreCookieModified"|"CacheControlNoStoreHTTPOnlyCookieModified"|"NoResponseHead"|"Unknown"|"ActivationNavigationsDisallowedForBug1234857"|"ErrorDocument"|"FencedFramesEmbedder"|"WebSocket"|"WebTransport"|"WebRTC"|"MainResourceHasCacheControlNoStore"|"MainResourceHasCacheControlNoCache"|"SubresourceHasCacheControlNoStore"|"SubresourceHasCacheControlNoCache"|"ContainsPlugins"|"DocumentLoaded"|"DedicatedWorkerOrWorklet"|"OutstandingNetworkRequestOthers"|"OutstandingIndexedDBTransaction"|"RequestedNotificationsPermission"|"RequestedMIDIPermission"|"RequestedAudioCapturePermission"|"RequestedVideoCapturePermission"|"RequestedBackForwardCacheBlockedSensors"|"RequestedBackgroundWorkPermission"|"BroadcastChannel"|"IndexedDBConnection"|"WebXR"|"SharedWorker"|"WebLocks"|"WebHID"|"WebShare"|"RequestedStorageAccessGrant"|"WebNfc"|"OutstandingNetworkRequestFetch"|"OutstandingNetworkRequestXHR"|"AppBanner"|"Printing"|"WebDatabase"|"PictureInPicture"|"Portal"|"SpeechRecognizer"|"IdleManager"|"PaymentManager"|"SpeechSynthesis"|"KeyboardLock"|"WebOTPService"|"OutstandingNetworkRequestDirectSocket"|"InjectedJavascript"|"InjectedStyleSheet"|"Dummy"|"ContentSecurityHandler"|"ContentWebAuthenticationAPI"|"ContentFileChooser"|"ContentSerial"|"ContentFileSystemAccess"|"ContentMediaDevicesDispatcherHost"|"ContentWebBluetooth"|"ContentWebUSB"|"ContentMediaSessionService"|"ContentScreenReader"|"EmbedderPopupBlockerTabHelper"|"EmbedderSafeBrowsingTriggeredPopupBlocker"|"EmbedderSafeBrowsingThreatDetails"|"EmbedderAppBannerManager"|"EmbedderDomDistillerViewerSource"|"EmbedderDomDistillerSelfDeletingRequestDelegate"|"EmbedderOomInterventionTabHelper"|"EmbedderOfflinePage"|"EmbedderChromePasswordManagerClientBindCredentialManager"|"EmbedderPermissionRequestManager"|"EmbedderModalDialog"|"EmbedderExtensions"|"EmbedderExtensionMessaging"|"EmbedderExtensionMessagingForOpenPort"|"EmbedderExtensionSentMessageToCachedFrame";
/** /**
* Types of not restored reasons for back-forward cache. * Types of not restored reasons for back-forward cache.
*/ */
@ -10720,6 +10756,11 @@ dependent on the reason:
* JavaScript stack trace of when frame was attached, only set if frame initiated from script. * JavaScript stack trace of when frame was attached, only set if frame initiated from script.
*/ */
stack?: Runtime.StackTrace; stack?: Runtime.StackTrace;
/**
* Identifies the bottom-most script which caused the frame to be labelled
as an ad. Only sent if frame is labelled as an ad and id is available.
*/
adScriptId?: AdScriptId;
} }
/** /**
* Fired when frame no longer has a scheduled navigation. * Fired when frame no longer has a scheduled navigation.
@ -12668,6 +12709,15 @@ Tokens from that issuer.
name: string; name: string;
} }
/**
* Returns a storage key given a frame id.
*/
export type getStorageKeyForFrameParameters = {
frameId: Page.FrameId;
}
export type getStorageKeyForFrameReturnValue = {
storageKey: SerializedStorageKey;
}
/** /**
* Clears storage for origin. * Clears storage for origin.
*/ */
@ -15248,13 +15298,30 @@ of scripts is used as end of range.
export type removeBreakpointReturnValue = { export type removeBreakpointReturnValue = {
} }
/** /**
* Restarts particular call frame from the beginning. * Restarts particular call frame from the beginning. The old, deprecated
behavior of `restartFrame` is to stay paused and allow further CDP commands
after a restart was scheduled. This can cause problems with restarting, so
we now continue execution immediatly after it has been scheduled until we
reach the beginning of the restarted frame.
To stay back-wards compatible, `restartFrame` now expects a `mode`
parameter to be present. If the `mode` parameter is missing, `restartFrame`
errors out.
The various return values are deprecated and `callFrames` is always empty.
Use the call frames from the `Debugger#paused` events instead, that fires
once V8 pauses at the beginning of the restarted function.
*/ */
export type restartFrameParameters = { export type restartFrameParameters = {
/** /**
* Call frame identifier to evaluate on. * Call frame identifier to evaluate on.
*/ */
callFrameId: CallFrameId; callFrameId: CallFrameId;
/**
* The `mode` parameter must be present and set to 'StepInto', otherwise
`restartFrame` will error out.
*/
mode?: "StepInto";
} }
export type restartFrameReturnValue = { export type restartFrameReturnValue = {
/** /**
@ -17514,7 +17581,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;
"DOMStorage.getStorageKeyForFrame": DOMStorage.getStorageKeyForFrameParameters;
"Database.disable": Database.disableParameters; "Database.disable": Database.disableParameters;
"Database.enable": Database.enableParameters; "Database.enable": Database.enableParameters;
"Database.executeSQL": Database.executeSQLParameters; "Database.executeSQL": Database.executeSQLParameters;
@ -17547,6 +17613,7 @@ Error was thrown.
"Emulation.setTimezoneOverride": Emulation.setTimezoneOverrideParameters; "Emulation.setTimezoneOverride": Emulation.setTimezoneOverrideParameters;
"Emulation.setVisibleSize": Emulation.setVisibleSizeParameters; "Emulation.setVisibleSize": Emulation.setVisibleSizeParameters;
"Emulation.setDisabledImageTypes": Emulation.setDisabledImageTypesParameters; "Emulation.setDisabledImageTypes": Emulation.setDisabledImageTypesParameters;
"Emulation.setHardwareConcurrencyOverride": Emulation.setHardwareConcurrencyOverrideParameters;
"Emulation.setUserAgentOverride": Emulation.setUserAgentOverrideParameters; "Emulation.setUserAgentOverride": Emulation.setUserAgentOverrideParameters;
"Emulation.setAutomationOverride": Emulation.setAutomationOverrideParameters; "Emulation.setAutomationOverride": Emulation.setAutomationOverrideParameters;
"HeadlessExperimental.beginFrame": HeadlessExperimental.beginFrameParameters; "HeadlessExperimental.beginFrame": HeadlessExperimental.beginFrameParameters;
@ -17744,6 +17811,7 @@ Error was thrown.
"ServiceWorker.stopWorker": ServiceWorker.stopWorkerParameters; "ServiceWorker.stopWorker": ServiceWorker.stopWorkerParameters;
"ServiceWorker.unregister": ServiceWorker.unregisterParameters; "ServiceWorker.unregister": ServiceWorker.unregisterParameters;
"ServiceWorker.updateRegistration": ServiceWorker.updateRegistrationParameters; "ServiceWorker.updateRegistration": ServiceWorker.updateRegistrationParameters;
"Storage.getStorageKeyForFrame": Storage.getStorageKeyForFrameParameters;
"Storage.clearDataForOrigin": Storage.clearDataForOriginParameters; "Storage.clearDataForOrigin": Storage.clearDataForOriginParameters;
"Storage.getCookies": Storage.getCookiesParameters; "Storage.getCookies": Storage.getCookiesParameters;
"Storage.setCookies": Storage.setCookiesParameters; "Storage.setCookies": Storage.setCookiesParameters;
@ -18044,7 +18112,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;
"DOMStorage.getStorageKeyForFrame": DOMStorage.getStorageKeyForFrameReturnValue;
"Database.disable": Database.disableReturnValue; "Database.disable": Database.disableReturnValue;
"Database.enable": Database.enableReturnValue; "Database.enable": Database.enableReturnValue;
"Database.executeSQL": Database.executeSQLReturnValue; "Database.executeSQL": Database.executeSQLReturnValue;
@ -18077,6 +18144,7 @@ Error was thrown.
"Emulation.setTimezoneOverride": Emulation.setTimezoneOverrideReturnValue; "Emulation.setTimezoneOverride": Emulation.setTimezoneOverrideReturnValue;
"Emulation.setVisibleSize": Emulation.setVisibleSizeReturnValue; "Emulation.setVisibleSize": Emulation.setVisibleSizeReturnValue;
"Emulation.setDisabledImageTypes": Emulation.setDisabledImageTypesReturnValue; "Emulation.setDisabledImageTypes": Emulation.setDisabledImageTypesReturnValue;
"Emulation.setHardwareConcurrencyOverride": Emulation.setHardwareConcurrencyOverrideReturnValue;
"Emulation.setUserAgentOverride": Emulation.setUserAgentOverrideReturnValue; "Emulation.setUserAgentOverride": Emulation.setUserAgentOverrideReturnValue;
"Emulation.setAutomationOverride": Emulation.setAutomationOverrideReturnValue; "Emulation.setAutomationOverride": Emulation.setAutomationOverrideReturnValue;
"HeadlessExperimental.beginFrame": HeadlessExperimental.beginFrameReturnValue; "HeadlessExperimental.beginFrame": HeadlessExperimental.beginFrameReturnValue;
@ -18274,6 +18342,7 @@ Error was thrown.
"ServiceWorker.stopWorker": ServiceWorker.stopWorkerReturnValue; "ServiceWorker.stopWorker": ServiceWorker.stopWorkerReturnValue;
"ServiceWorker.unregister": ServiceWorker.unregisterReturnValue; "ServiceWorker.unregister": ServiceWorker.unregisterReturnValue;
"ServiceWorker.updateRegistration": ServiceWorker.updateRegistrationReturnValue; "ServiceWorker.updateRegistration": ServiceWorker.updateRegistrationReturnValue;
"Storage.getStorageKeyForFrame": Storage.getStorageKeyForFrameReturnValue;
"Storage.clearDataForOrigin": Storage.clearDataForOriginReturnValue; "Storage.clearDataForOrigin": Storage.clearDataForOriginReturnValue;
"Storage.getCookies": Storage.getCookiesReturnValue; "Storage.getCookies": Storage.getCookiesReturnValue;
"Storage.setCookies": Storage.setCookiesReturnValue; "Storage.setCookies": Storage.setCookiesReturnValue;

View file

@ -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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 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/103.0.5060.53 Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 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/103.0.5060.53 Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Safari/537.36",
"viewport": { "viewport": {
"width": 1138, "width": 1138,
"height": 712 "height": 712
@ -858,7 +858,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 384, "width": 384,
"height": 640 "height": 640
@ -869,7 +869,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 384 "height": 384
@ -880,7 +880,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36 Edge/14.14263",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -891,7 +891,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36 Edge/14.14263",
"viewport": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -902,7 +902,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36 Edge/14.14263",
"viewport": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -913,7 +913,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36 Edge/14.14263",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -924,7 +924,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/103.0.5060.53 Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Safari/537.36",
"viewport": { "viewport": {
"width": 800, "width": 800,
"height": 1280 "height": 1280
@ -935,7 +935,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/103.0.5060.53 Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Safari/537.36",
"viewport": { "viewport": {
"width": 1280, "width": 1280,
"height": 800 "height": 800
@ -946,7 +946,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 384, "width": 384,
"height": 640 "height": 640
@ -957,7 +957,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 384 "height": 384
@ -968,7 +968,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -979,7 +979,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -990,7 +990,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 412, "width": 412,
"height": 732 "height": 732
@ -1001,7 +1001,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 732, "width": 732,
"height": 412 "height": 412
@ -1012,7 +1012,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 412, "width": 412,
"height": 732 "height": 732
@ -1023,7 +1023,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 732, "width": 732,
"height": 412 "height": 412
@ -1034,7 +1034,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 412, "width": 412,
"height": 732 "height": 732
@ -1045,7 +1045,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 732, "width": 732,
"height": 412 "height": 412
@ -1056,7 +1056,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/103.0.5060.53 Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Safari/537.36",
"viewport": { "viewport": {
"width": 600, "width": 600,
"height": 960 "height": 960
@ -1067,7 +1067,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/103.0.5060.53 Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Safari/537.36",
"viewport": { "viewport": {
"width": 960, "width": 960,
"height": 600 "height": 600
@ -1122,7 +1122,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 411, "width": 411,
"height": 731 "height": 731
@ -1133,7 +1133,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 731, "width": 731,
"height": 411 "height": 411
@ -1144,7 +1144,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 411, "width": 411,
"height": 823 "height": 823
@ -1155,7 +1155,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 823, "width": 823,
"height": 411 "height": 411
@ -1166,7 +1166,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 393, "width": 393,
"height": 786 "height": 786
@ -1177,7 +1177,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/103.0.5060.53 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/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 786, "width": 786,
"height": 393 "height": 393
@ -1188,7 +1188,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 353, "width": 353,
"height": 745 "height": 745
@ -1199,7 +1199,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 745, "width": 745,
"height": 353 "height": 353
@ -1210,7 +1210,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"screen": { "screen": {
"width": 412, "width": 412,
"height": 892 "height": 892
@ -1225,7 +1225,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"screen": { "screen": {
"height": 892, "height": 892,
"width": 412 "width": 412
@ -1240,7 +1240,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"screen": { "screen": {
"width": 393, "width": 393,
"height": 851 "height": 851
@ -1255,7 +1255,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"screen": { "screen": {
"width": 851, "width": 851,
"height": 393 "height": 393
@ -1270,7 +1270,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -1281,7 +1281,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/103.0.5060.53 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -1292,7 +1292,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/103.0.5060.53 Safari/537.36", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Safari/537.36",
"screen": { "screen": {
"width": 1792, "width": 1792,
"height": 1120 "height": 1120
@ -1307,7 +1307,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/103.0.5060.53 Safari/537.36 Edg/103.0.5060.53", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Safari/537.36 Edg/104.0.5112.20",
"screen": { "screen": {
"width": 1792, "width": 1792,
"height": 1120 "height": 1120
@ -1352,7 +1352,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/103.0.5060.53 Safari/537.36", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Safari/537.36",
"screen": { "screen": {
"width": 1920, "width": 1920,
"height": 1080 "height": 1080
@ -1367,7 +1367,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/103.0.5060.53 Safari/537.36 Edg/103.0.5060.53", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.20 Safari/537.36 Edg/104.0.5112.20",
"screen": { "screen": {
"width": 1920, "width": 1920,
"height": 1080 "height": 1080

View file

@ -213,8 +213,13 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
return { session: new CDPSessionDispatcher(this._scope, await crBrowserContext.newCDPSession((params.page ? params.page as PageDispatcher : params.frame as FrameDispatcher)._object)) }; return { session: new CDPSessionDispatcher(this._scope, await crBrowserContext.newCDPSession((params.page ? params.page as PageDispatcher : params.frame as FrameDispatcher)._object)) };
} }
async harStart(params: channels.BrowserContextHarStartParams): Promise<channels.BrowserContextHarStartResult> {
const harId = await this._context._harStart(params.page ? (params.page as PageDispatcher)._object : null, params.options);
return { harId };
}
async harExport(params: channels.BrowserContextHarExportParams): Promise<channels.BrowserContextHarExportResult> { async harExport(params: channels.BrowserContextHarExportParams): Promise<channels.BrowserContextHarExportResult> {
const artifact = await this._context._harRecorder?.export(); const artifact = await this._context._harExport(params.harId);
if (!artifact) if (!artifact)
throw new Error('No HAR artifact. Ensure record.harPath is set.'); throw new Error('No HAR artifact. Ensure record.harPath is set.');
return { artifact: new ArtifactDispatcher(this._scope, artifact) }; return { artifact: new ArtifactDispatcher(this._scope, artifact) };

View file

@ -24,7 +24,7 @@ import type { DispatcherScope } from './dispatcher';
import { Dispatcher } from './dispatcher'; import { Dispatcher } from './dispatcher';
import { yazl, yauzl } from '../../zipBundle'; import { yazl, yauzl } from '../../zipBundle';
import { ZipFile } from '../../utils/zipFile'; import { ZipFile } from '../../utils/zipFile';
import type { HAREntry, HARFile, HARHeader } from '../../../types/har'; import type * as har from '../har/har';
import type { HeadersArray } from '../types'; import type { HeadersArray } from '../types';
export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.LocalUtilsChannel> implements channels.LocalUtilsChannel { export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.LocalUtilsChannel> implements channels.LocalUtilsChannel {
@ -100,10 +100,10 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
if (!harEntryName) if (!harEntryName)
return { error: 'Specified archive does not have a .har file' }; return { error: 'Specified archive does not have a .har file' };
const har = await zipFile.read(harEntryName); const har = await zipFile.read(harEntryName);
const harFile = JSON.parse(har.toString()) as HARFile; const harFile = JSON.parse(har.toString()) as har.HARFile;
harBackend = new HarBackend(harFile, null, zipFile); harBackend = new HarBackend(harFile, null, zipFile);
} else { } else {
const harFile = JSON.parse(await fs.promises.readFile(params.file, 'utf-8')) as HARFile; const harFile = JSON.parse(await fs.promises.readFile(params.file, 'utf-8')) as har.HARFile;
harBackend = new HarBackend(harFile, path.dirname(params.file), null); harBackend = new HarBackend(harFile, path.dirname(params.file), null);
} }
this._harBakends.set(harBackend.id, harBackend); this._harBakends.set(harBackend.id, harBackend);
@ -124,17 +124,31 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
harBackend.dispose(); harBackend.dispose();
} }
} }
async harUnzip(params: channels.LocalUtilsHarUnzipParams, metadata?: channels.Metadata): 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);
}
} }
const redirectStatus = [301, 302, 303, 307, 308]; const redirectStatus = [301, 302, 303, 307, 308];
class HarBackend { class HarBackend {
readonly id = createGuid(); readonly id = createGuid();
private _harFile: HARFile; private _harFile: har.HARFile;
private _zipFile: ZipFile | null; private _zipFile: ZipFile | null;
private _baseDir: string | null; private _baseDir: string | null;
constructor(harFile: HARFile, baseDir: string | null, zipFile: ZipFile | null) { constructor(harFile: har.HARFile, baseDir: string | null, zipFile: ZipFile | null) {
this._harFile = harFile; this._harFile = harFile;
this._baseDir = baseDir; this._baseDir = baseDir;
this._zipFile = zipFile; this._zipFile = zipFile;
@ -176,25 +190,25 @@ class HarBackend {
} }
} }
private async _loadContent(content: { text?: string, encoding?: string, _sha1?: string }): Promise<Buffer> { private async _loadContent(content: { text?: string, encoding?: string, _file?: string }): Promise<Buffer> {
const sha1 = content._sha1; const file = content._file;
let buffer: Buffer; let buffer: Buffer;
if (sha1) { if (file) {
if (this._zipFile) if (this._zipFile)
buffer = await this._zipFile.read(sha1); buffer = await this._zipFile.read(file);
else else
buffer = await fs.promises.readFile(path.resolve(this._baseDir!, sha1)); buffer = await fs.promises.readFile(path.resolve(this._baseDir!, file));
} else { } else {
buffer = Buffer.from(content.text || '', content.encoding === 'base64' ? 'base64' : 'utf-8'); buffer = Buffer.from(content.text || '', content.encoding === 'base64' ? 'base64' : 'utf-8');
} }
return buffer; return buffer;
} }
private async _harFindResponse(url: string, method: string, headers: HeadersArray, postData: Buffer | undefined): Promise<HAREntry | undefined> { private async _harFindResponse(url: string, method: string, headers: HeadersArray, postData: Buffer | undefined): Promise<har.Entry | undefined> {
const harLog = this._harFile.log; const harLog = this._harFile.log;
const visited = new Set<HAREntry>(); const visited = new Set<har.Entry>();
while (true) { while (true) {
const entries: HAREntry[] = []; const entries: har.Entry[] = [];
for (const candidate of harLog.entries) { for (const candidate of harLog.entries) {
if (candidate.request.url !== url || candidate.request.method !== method) if (candidate.request.url !== url || candidate.request.method !== method)
continue; continue;
@ -213,7 +227,7 @@ class HarBackend {
// Disambiguate using headers - then one with most matching headers wins. // Disambiguate using headers - then one with most matching headers wins.
if (entries.length > 1) { if (entries.length > 1) {
const list: { candidate: HAREntry, matchingHeaders: number }[] = []; const list: { candidate: har.Entry, matchingHeaders: number }[] = [];
for (const candidate of entries) { for (const candidate of entries) {
const matchingHeaders = countMatchingHeaders(candidate.request.headers, headers); const matchingHeaders = countMatchingHeaders(candidate.request.headers, headers);
list.push({ candidate, matchingHeaders }); list.push({ candidate, matchingHeaders });
@ -249,7 +263,7 @@ class HarBackend {
} }
} }
function countMatchingHeaders(harHeaders: HARHeader[], headers: HeadersArray): number { function countMatchingHeaders(harHeaders: har.Header[], headers: HeadersArray): number {
const set = new Set(headers.map(h => h.name.toLowerCase() + ':' + h.value)); const set = new Set(headers.map(h => h.name.toLowerCase() + ':' + h.value));
let matches = 0; let matches = 0;
for (const h of harHeaders) { for (const h of harHeaders) {

View file

@ -135,7 +135,11 @@ export class RouteDispatcher extends Dispatcher<Route, channels.RouteChannel> im
} }
async abort(params: channels.RouteAbortParams): Promise<void> { async abort(params: channels.RouteAbortParams): Promise<void> {
await this._object.abort(params.errorCode || 'failed', params.redirectAbortedNavigationToUrl); await this._object.abort(params.errorCode || 'failed');
}
async redirectNavigationRequest(params: channels.RouteRedirectNavigationRequestParams): Promise<void> {
await this._object.redirectNavigationRequest(params.url);
} }
} }

View file

@ -269,7 +269,7 @@ export class FrameManager {
name: frame._name, name: frame._name,
newDocument: frame.pendingDocument(), newDocument: frame.pendingDocument(),
error: new NavigationAbortedError(documentId, errorText), error: new NavigationAbortedError(documentId, errorText),
isPublic: !frame._pendingNavigationRedirectAfterAbort isPublic: !(documentId && frame._redirectedNavigations.has(documentId)),
}; };
frame.setPendingDocument(undefined); frame.setPendingDocument(undefined);
frame.emit(Frame.Events.InternalNavigation, navigationEvent); frame.emit(Frame.Events.InternalNavigation, navigationEvent);
@ -467,7 +467,7 @@ export class Frame extends SdkObject {
readonly _detachedPromise: Promise<void>; readonly _detachedPromise: Promise<void>;
private _detachedCallback = () => {}; private _detachedCallback = () => {};
private _raceAgainstEvaluationStallingEventsPromises = new Set<ManualPromise<any>>(); private _raceAgainstEvaluationStallingEventsPromises = new Set<ManualPromise<any>>();
_pendingNavigationRedirectAfterAbort: { url: string, documentId: string } | undefined; readonly _redirectedNavigations = new Map<string, { url: string, gotoPromise: Promise<network.Response | null> }>(); // documentId -> data
constructor(page: Page, id: string, parentFrame: Frame | null) { constructor(page: Page, id: string, parentFrame: Frame | null) {
super(page, 'frame'); super(page, 'frame');
@ -604,12 +604,11 @@ export class Frame extends SdkObject {
this._page._crashedPromise.then(() => { throw new Error('Navigation failed because page crashed!'); }), this._page._crashedPromise.then(() => { throw new Error('Navigation failed because page crashed!'); }),
this._detachedPromise.then(() => { throw new Error('Navigating frame was detached!'); }), this._detachedPromise.then(() => { throw new Error('Navigating frame was detached!'); }),
action().catch(e => { action().catch(e => {
if (this._pendingNavigationRedirectAfterAbort && e instanceof NavigationAbortedError) { if (e instanceof NavigationAbortedError && e.documentId) {
const { url, documentId } = this._pendingNavigationRedirectAfterAbort; const data = this._redirectedNavigations.get(e.documentId);
this._pendingNavigationRedirectAfterAbort = undefined; if (data) {
if (e.documentId === documentId) { progress.log(`waiting for redirected navigation to "${data.url}"`);
progress.log(`redirecting navigation to "${url}"`); return data.gotoPromise;
return this._gotoAction(progress, url, options);
} }
} }
throw e; throw e;
@ -617,8 +616,14 @@ export class Frame extends SdkObject {
]); ]);
} }
redirectNavigationAfterAbort(url: string, documentId: string) { redirectNavigation(url: string, documentId: string, referer: string | undefined) {
this._pendingNavigationRedirectAfterAbort = { url, documentId }; const controller = new ProgressController(serverSideCallMetadata(), this);
const data = {
url,
gotoPromise: controller.run(progress => this._gotoAction(progress, url, { referer }), 0),
};
this._redirectedNavigations.set(documentId, data);
data.gotoPromise.finally(() => this._redirectedNavigations.delete(documentId));
} }
async goto(metadata: CallMetadata, url: string, options: types.GotoOptions = {}): Promise<network.Response | null> { async goto(metadata: CallMetadata, url: string, options: types.GotoOptions = {}): Promise<network.Response | null> {
@ -659,7 +664,7 @@ export class Frame extends SdkObject {
if (event.newDocument!.documentId !== navigateResult.newDocumentId) { if (event.newDocument!.documentId !== navigateResult.newDocumentId) {
// This is just a sanity check. In practice, new navigation should // This is just a sanity check. In practice, new navigation should
// cancel the previous one and report "request cancelled"-like error. // cancel the previous one and report "request cancelled"-like error.
throw new Error('Navigation interrupted by another one'); throw new NavigationAbortedError(navigateResult.newDocumentId, 'Navigation interrupted by another one');
} }
if (event.error) if (event.error)
throw event.error; throw event.error;

View file

@ -22,19 +22,22 @@ export type HARFile = {
export type Log = { export type Log = {
version: string; version: string;
creator: Creator; creator: Creator;
browser: Browser; browser?: Browser;
pages: Page[]; pages?: Page[];
entries: Entry[]; entries: Entry[];
comment?: string;
}; };
export type Creator = { export type Creator = {
name: string; name: string;
version: string; version: string;
comment?: string;
}; };
export type Browser = { export type Browser = {
name: string; name: string;
version: string; version: string;
comment?: string;
}; };
export type Page = { export type Page = {
@ -42,11 +45,13 @@ export type Page = {
id: string; id: string;
title: string; title: string;
pageTimings: PageTimings; pageTimings: PageTimings;
comment?: string;
}; };
export type PageTimings = { export type PageTimings = {
onContentLoad: number; onContentLoad?: number;
onLoad: number; onLoad?: number;
comment?: string;
}; };
export type Entry = { export type Entry = {
@ -59,9 +64,8 @@ export type Entry = {
timings: Timings; timings: Timings;
serverIPAddress?: string; serverIPAddress?: string;
connection?: string; connection?: string;
_requestref: string; _frameref?: string;
_frameref: string; _monotonicTime?: number;
_monotonicTime: number;
_serverPort?: number; _serverPort?: number;
_securityDetails?: SecurityDetails; _securityDetails?: SecurityDetails;
}; };
@ -76,6 +80,7 @@ export type Request = {
postData?: PostData; postData?: PostData;
headersSize: number; headersSize: number;
bodySize: number; bodySize: number;
comment?: string;
}; };
export type Response = { export type Response = {
@ -88,7 +93,8 @@ export type Response = {
redirectURL: string; redirectURL: string;
headersSize: number; headersSize: number;
bodySize: number; bodySize: number;
_transferSize: number; comment?: string;
_transferSize?: number;
_failureText?: string _failureText?: string
}; };
@ -101,23 +107,28 @@ export type Cookie = {
httpOnly?: boolean; httpOnly?: boolean;
secure?: boolean; secure?: boolean;
sameSite?: string; sameSite?: string;
comment?: string;
}; };
export type Header = { export type Header = {
name: string; name: string;
value: string; value: string;
comment?: string;
}; };
export type QueryParameter = { export type QueryParameter = {
name: string; name: string;
value: string; value: string;
comment?: string;
}; };
export type PostData = { export type PostData = {
mimeType: string; mimeType: string;
params: Param[]; params: Param[];
text: string; text: string;
comment?: string;
_sha1?: string; _sha1?: string;
_file?: string;
}; };
export type Param = { export type Param = {
@ -125,6 +136,7 @@ export type Param = {
value?: string; value?: string;
fileName?: string; fileName?: string;
contentType?: string; contentType?: string;
comment?: string;
}; };
export type Content = { export type Content = {
@ -133,12 +145,15 @@ export type Content = {
mimeType: string; mimeType: string;
text?: string; text?: string;
encoding?: string; encoding?: string;
comment?: string;
_sha1?: string; _sha1?: string;
_file?: string;
}; };
export type Cache = { export type Cache = {
beforeRequest: CacheState | null; beforeRequest?: CacheState | null;
afterRequest: CacheState | null; afterRequest?: CacheState | null;
comment?: string;
}; };
export type CacheState = { export type CacheState = {
@ -146,6 +161,7 @@ export type CacheState = {
lastAccess: string; lastAccess: string;
eTag: string; eTag: string;
hitCount: number; hitCount: number;
comment?: string;
}; };
export type Timings = { export type Timings = {
@ -156,6 +172,7 @@ export type Timings = {
wait: number; wait: number;
receive: number; receive: number;
ssl?: number; ssl?: number;
comment?: string;
}; };
export type SecurityDetails = { export type SecurityDetails = {

View file

@ -26,6 +26,7 @@ import type { ZipFile } from '../../zipBundle';
import { ManualPromise } from '../../utils/manualPromise'; import { ManualPromise } from '../../utils/manualPromise';
import type EventEmitter from 'events'; import type EventEmitter from 'events';
import { createGuid } from '../../utils'; import { createGuid } from '../../utils';
import type { Page } from '../page';
export class HarRecorder { export class HarRecorder {
private _artifact: Artifact; private _artifact: Artifact;
@ -33,14 +34,17 @@ export class HarRecorder {
private _tracer: HarTracer; private _tracer: HarTracer;
private _entries: har.Entry[] = []; private _entries: har.Entry[] = [];
private _zipFile: ZipFile | null = null; private _zipFile: ZipFile | null = null;
private _writtenZipEntries = new Set<string>();
constructor(context: BrowserContext, options: channels.RecordHarOptions) { constructor(context: BrowserContext, page: Page | null, options: channels.RecordHarOptions) {
this._artifact = new Artifact(context, path.join(context._browser.options.artifactsDir, `${createGuid()}.har`)); this._artifact = new Artifact(context, path.join(context._browser.options.artifactsDir, `${createGuid()}.har`));
const urlFilterRe = options.urlRegexSource !== undefined && options.urlRegexFlags !== undefined ? new RegExp(options.urlRegexSource, options.urlRegexFlags) : undefined; const urlFilterRe = options.urlRegexSource !== undefined && options.urlRegexFlags !== undefined ? new RegExp(options.urlRegexSource, options.urlRegexFlags) : undefined;
const expectsZip = options.path.endsWith('.zip'); const expectsZip = options.path.endsWith('.zip');
const content = options.content || (expectsZip ? 'attach' : 'embed'); const content = options.content || (expectsZip ? 'attach' : 'embed');
this._tracer = new HarTracer(context, this, { this._tracer = new HarTracer(context, page, this, {
content, content,
slimMode: options.mode === 'minimal',
includeTraceInfo: false,
waitForContentOnStop: true, waitForContentOnStop: true,
skipScripts: false, skipScripts: false,
urlFilter: urlFilterRe ?? options.urlGlob, urlFilter: urlFilterRe ?? options.urlGlob,
@ -57,7 +61,9 @@ export class HarRecorder {
} }
onContentBlob(sha1: string, buffer: Buffer) { onContentBlob(sha1: string, buffer: Buffer) {
if (this._zipFile) if (!this._zipFile || this._writtenZipEntries.has(sha1))
return;
this._writtenZipEntries.add(sha1);
this._zipFile!.addBuffer(buffer, sha1); this._zipFile!.addBuffer(buffer, sha1);
} }
@ -70,7 +76,7 @@ export class HarRecorder {
const log = this._tracer.stop(); const log = this._tracer.stop();
log.entries = this._entries; log.entries = this._entries;
const harFileContent = JSON.stringify({ log }, undefined, 2); const harFileContent = jsonStringify({ log });
if (this._zipFile) { if (this._zipFile) {
const result = new ManualPromise<void>(); const result = new ManualPromise<void>();
@ -92,3 +98,50 @@ export class HarRecorder {
return this._artifact; return this._artifact;
} }
} }
function jsonStringify(object: any): string {
const tokens: string[] = [];
innerJsonStringify(object, tokens, '', false, undefined);
return tokens.join('');
}
function innerJsonStringify(object: any, tokens: string[], indent: string, flat: boolean, parentKey: string | undefined) {
if (typeof object !== 'object' || object === null) {
tokens.push(JSON.stringify(object));
return;
}
const isArray = Array.isArray(object);
if (!isArray && object.constructor.name !== 'Object') {
tokens.push(JSON.stringify(object));
return;
}
const entries = isArray ? object : Object.entries(object).filter(e => e[1] !== undefined);
if (!entries.length) {
tokens.push(isArray ? `[]` : `{}`);
return;
}
const childIndent = `${indent} `;
let brackets: { open: string, close: string };
if (isArray)
brackets = flat ? { open: '[', close: ']' } : { open: `[\n${childIndent}`, close: `\n${indent}]` };
else
brackets = flat ? { open: '{ ', close: ' }' } : { open: `{\n${childIndent}`, close: `\n${indent}}` };
tokens.push(brackets.open);
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
if (i)
tokens.push(flat ? `, ` : `,\n${childIndent}`);
if (!isArray)
tokens.push(`${JSON.stringify(entry[0])}: `);
const key = isArray ? undefined : entry[0];
const flatten = flat || key === 'timings' || parentKey === 'headers';
innerJsonStringify(isArray ? entry : entry[1], tokens, childIndent, flatten, key);
}
tokens.push(brackets.close);
}

View file

@ -42,8 +42,16 @@ export interface HarTracerDelegate {
type HarTracerOptions = { type HarTracerOptions = {
content: 'omit' | 'attach' | 'embed'; content: 'omit' | 'attach' | 'embed';
skipScripts: boolean; skipScripts: boolean;
includeTraceInfo: boolean;
waitForContentOnStop: boolean; waitForContentOnStop: boolean;
urlFilter?: string | RegExp; urlFilter?: string | RegExp;
slimMode?: boolean;
omitSecurityDetails?: boolean;
omitCookies?: boolean;
omitTiming?: boolean;
omitServerIP?: boolean;
omitPages?: boolean;
omitSizes?: boolean;
}; };
export class HarTracer { export class HarTracer {
@ -56,11 +64,21 @@ export class HarTracer {
private _started = false; private _started = false;
private _entrySymbol: symbol; private _entrySymbol: symbol;
private _baseURL: string | undefined; private _baseURL: string | undefined;
private _page: Page | null;
constructor(context: BrowserContext | APIRequestContext, delegate: HarTracerDelegate, options: HarTracerOptions) { constructor(context: BrowserContext | APIRequestContext, page: Page | null, delegate: HarTracerDelegate, options: HarTracerOptions) {
this._context = context; this._context = context;
this._page = page;
this._delegate = delegate; this._delegate = delegate;
this._options = options; this._options = options;
if (options.slimMode) {
options.omitSecurityDetails = true;
options.omitCookies = true;
options.omitTiming = true;
options.omitServerIP = true;
options.omitSizes = true;
options.omitPages = true;
}
this._entrySymbol = Symbol('requestHarEntry'); this._entrySymbol = Symbol('requestHarEntry');
this._baseURL = context instanceof APIRequestContext ? context._defaultOptions().baseURL : context._options.baseURL; this._baseURL = context instanceof APIRequestContext ? context._defaultOptions().baseURL : context._options.baseURL;
} }
@ -76,7 +94,7 @@ export class HarTracer {
]; ];
if (this._context instanceof BrowserContext) { if (this._context instanceof BrowserContext) {
this._eventListeners.push( this._eventListeners.push(
eventsHelper.addEventListener(this._context, BrowserContext.Events.Page, (page: Page) => this._ensurePageEntry(page)), eventsHelper.addEventListener(this._context, BrowserContext.Events.Page, (page: Page) => this._createPageEntryIfNeeded(page)),
eventsHelper.addEventListener(this._context, BrowserContext.Events.Request, (request: network.Request) => this._onRequest(request)), eventsHelper.addEventListener(this._context, BrowserContext.Events.Request, (request: network.Request) => this._onRequest(request)),
eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestFinished, ({ request, response }) => this._onRequestFinished(request, response).catch(() => {})), eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestFinished, ({ request, response }) => this._onRequestFinished(request, response).catch(() => {})),
eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestFailed, request => this._onRequestFailed(request)), eventsHelper.addEventListener(this._context, BrowserContext.Events.RequestFailed, request => this._onRequestFailed(request)),
@ -92,32 +110,36 @@ export class HarTracer {
return (request as any)[this._entrySymbol]; return (request as any)[this._entrySymbol];
} }
private _ensurePageEntry(page: Page) { private _createPageEntryIfNeeded(page: Page): har.Page | undefined {
if (this._options.omitPages)
return;
if (this._page && page !== this._page)
return;
let pageEntry = this._pageEntries.get(page); let pageEntry = this._pageEntries.get(page);
if (!pageEntry) { if (!pageEntry) {
page.mainFrame().on(Frame.Events.AddLifecycle, (event: LifecycleEvent) => {
if (event === 'load')
this._onLoad(page);
if (event === 'domcontentloaded')
this._onDOMContentLoaded(page);
});
pageEntry = { pageEntry = {
startedDateTime: new Date(), startedDateTime: new Date(),
id: page.guid, id: page.guid,
title: '', title: '',
pageTimings: { pageTimings: this._options.omitTiming ? {} : {
onContentLoad: -1, onContentLoad: -1,
onLoad: -1, onLoad: -1,
}, },
}; };
page.mainFrame().on(Frame.Events.AddLifecycle, (event: LifecycleEvent) => {
if (event === 'load')
this._onLoad(page, pageEntry!);
if (event === 'domcontentloaded')
this._onDOMContentLoaded(page, pageEntry!);
});
this._pageEntries.set(page, pageEntry); this._pageEntries.set(page, pageEntry);
} }
return pageEntry; return pageEntry;
} }
private _onDOMContentLoaded(page: Page) { private _onDOMContentLoaded(page: Page, pageEntry: har.Page) {
const pageEntry = this._ensurePageEntry(page);
const promise = page.mainFrame().evaluateExpression(String(() => { const promise = page.mainFrame().evaluateExpression(String(() => {
return { return {
title: document.title, title: document.title,
@ -125,13 +147,13 @@ export class HarTracer {
}; };
}), true, undefined, 'utility').then(result => { }), true, undefined, 'utility').then(result => {
pageEntry.title = result.title; pageEntry.title = result.title;
if (!this._options.omitTiming)
pageEntry.pageTimings.onContentLoad = result.domContentLoaded; pageEntry.pageTimings.onContentLoad = result.domContentLoaded;
}).catch(() => {}); }).catch(() => {});
this._addBarrier(page, promise); this._addBarrier(page, promise);
} }
private _onLoad(page: Page) { private _onLoad(page: Page, pageEntry: har.Page) {
const pageEntry = this._ensurePageEntry(page);
const promise = page.mainFrame().evaluateExpression(String(() => { const promise = page.mainFrame().evaluateExpression(String(() => {
return { return {
title: document.title, title: document.title,
@ -139,6 +161,7 @@ export class HarTracer {
}; };
}), true, undefined, 'utility').then(result => { }), true, undefined, 'utility').then(result => {
pageEntry.title = result.title; pageEntry.title = result.title;
if (!this._options.omitTiming)
pageEntry.pageTimings.onLoad = result.loaded; pageEntry.pageTimings.onLoad = result.loaded;
}).catch(() => {}); }).catch(() => {});
this._addBarrier(page, promise); this._addBarrier(page, promise);
@ -161,10 +184,12 @@ export class HarTracer {
private _onAPIRequest(event: APIRequestEvent) { private _onAPIRequest(event: APIRequestEvent) {
if (!this._shouldIncludeEntryWithUrl(event.url.toString())) if (!this._shouldIncludeEntryWithUrl(event.url.toString()))
return; return;
const harEntry = createHarEntry(event.method, event.url, '', ''); const harEntry = createHarEntry(event.method, event.url, undefined, this._options);
if (!this._options.omitCookies)
harEntry.request.cookies = event.cookies; harEntry.request.cookies = event.cookies;
harEntry.request.headers = Object.entries(event.headers).map(([name, value]) => ({ name, value })); harEntry.request.headers = Object.entries(event.headers).map(([name, value]) => ({ name, value }));
harEntry.request.postData = this._postDataForBuffer(event.postData || null, event.headers['content-type'], this._options.content); harEntry.request.postData = this._postDataForBuffer(event.postData || null, event.headers['content-type'], this._options.content);
if (!this._options.omitSizes)
harEntry.request.bodySize = event.postData?.length || 0; harEntry.request.bodySize = event.postData?.length || 0;
(event as any)[this._entrySymbol] = harEntry; (event as any)[this._entrySymbol] = harEntry;
if (this._started) if (this._started)
@ -186,7 +211,7 @@ export class HarTracer {
value: event.rawHeaders[i + 1] value: event.rawHeaders[i + 1]
}); });
} }
harEntry.response.cookies = event.cookies.map(c => { harEntry.response.cookies = this._options.omitCookies ? [] : event.cookies.map(c => {
return { return {
...c, ...c,
expires: c.expires === -1 ? undefined : new Date(c.expires) expires: c.expires === -1 ? undefined : new Date(c.expires)
@ -207,14 +232,18 @@ export class HarTracer {
if (!this._shouldIncludeEntryWithUrl(request.url())) if (!this._shouldIncludeEntryWithUrl(request.url()))
return; return;
const page = request.frame()._page; const page = request.frame()._page;
if (this._page && page !== this._page)
return;
const url = network.parsedURL(request.url()); const url = network.parsedURL(request.url());
if (!url) if (!url)
return; return;
const pageEntry = this._ensurePageEntry(page); const pageEntry = this._createPageEntryIfNeeded(page);
const harEntry = createHarEntry(request.method(), url, request.guid, request.frame().guid); const harEntry = createHarEntry(request.method(), url, request.frame().guid, this._options);
if (pageEntry)
harEntry.pageref = pageEntry.id; harEntry.pageref = pageEntry.id;
harEntry.request.postData = this._postDataForRequest(request, this._options.content); harEntry.request.postData = this._postDataForRequest(request, this._options.content);
if (!this._options.omitSizes)
harEntry.request.bodySize = request.bodySize(); harEntry.request.bodySize = request.bodySize();
if (request.redirectedFrom()) { if (request.redirectedFrom()) {
const fromEntry = this._entryForRequest(request.redirectedFrom()!); const fromEntry = this._entryForRequest(request.redirectedFrom()!);
@ -229,16 +258,16 @@ export class HarTracer {
private async _onRequestFinished(request: network.Request, response: network.Response | null) { private async _onRequestFinished(request: network.Request, response: network.Response | null) {
if (!response) if (!response)
return; return;
const page = request.frame()._page;
const harEntry = this._entryForRequest(request); const harEntry = this._entryForRequest(request);
if (!harEntry) if (!harEntry)
return; return;
const page = request.frame()._page;
const httpVersion = response.httpVersion(); const httpVersion = response.httpVersion();
harEntry.request.httpVersion = httpVersion; harEntry.request.httpVersion = httpVersion;
harEntry.response.httpVersion = httpVersion; harEntry.response.httpVersion = httpVersion;
const compressionCalculationBarrier = { const compressionCalculationBarrier = this._options.omitSizes ? undefined : {
_encodedBodySize: -1, _encodedBodySize: -1,
_decodedBodySize: -1, _decodedBodySize: -1,
barrier: new ManualPromise<void>(), barrier: new ManualPromise<void>(),
@ -257,33 +286,37 @@ export class HarTracer {
this._check(); this._check();
} }
}; };
if (compressionCalculationBarrier)
this._addBarrier(page, compressionCalculationBarrier.barrier); this._addBarrier(page, compressionCalculationBarrier.barrier);
const promise = response.body().then(buffer => { const promise = response.body().then(buffer => {
if (this._options.skipScripts && request.resourceType() === 'script') { if (this._options.skipScripts && request.resourceType() === 'script') {
compressionCalculationBarrier.setDecodedBodySize(0); compressionCalculationBarrier?.setDecodedBodySize(0);
return; return;
} }
const content = harEntry.response.content; const content = harEntry.response.content;
compressionCalculationBarrier.setDecodedBodySize(buffer.length); compressionCalculationBarrier?.setDecodedBodySize(buffer.length);
this._storeResponseContent(buffer, content, request.resourceType()); this._storeResponseContent(buffer, content, request.resourceType());
}).catch(() => { }).catch(() => {
compressionCalculationBarrier.setDecodedBodySize(0); compressionCalculationBarrier?.setDecodedBodySize(0);
}).then(() => { }).then(() => {
if (this._started) if (this._started)
this._delegate.onEntryFinished(harEntry); this._delegate.onEntryFinished(harEntry);
}); });
this._addBarrier(page, promise); this._addBarrier(page, promise);
if (!this._options.omitSizes) {
this._addBarrier(page, response.sizes().then(sizes => { this._addBarrier(page, response.sizes().then(sizes => {
harEntry.response.bodySize = sizes.responseBodySize; harEntry.response.bodySize = sizes.responseBodySize;
harEntry.response.headersSize = sizes.responseHeadersSize; harEntry.response.headersSize = sizes.responseHeadersSize;
// Fallback for WebKit by calculating it manually // Fallback for WebKit by calculating it manually
harEntry.response._transferSize = response.request().responseSize.transferSize || (sizes.responseHeadersSize + sizes.responseBodySize); harEntry.response._transferSize = response.request().responseSize.transferSize || (sizes.responseHeadersSize + sizes.responseBodySize);
harEntry.request.headersSize = sizes.requestHeadersSize; harEntry.request.headersSize = sizes.requestHeadersSize;
compressionCalculationBarrier.setEncodedBodySize(sizes.responseBodySize); compressionCalculationBarrier?.setEncodedBodySize(sizes.responseBodySize);
})); }));
} }
}
private async _onRequestFailed(request: network.Request) { private async _onRequestFailed(request: network.Request) {
const harEntry = this._entryForRequest(request); const harEntry = this._entryForRequest(request);
@ -301,7 +334,10 @@ export class HarTracer {
content.size = 0; content.size = 0;
return; return;
} }
if (!this._options.omitSizes)
content.size = buffer.length; content.size = buffer.length;
if (this._options.content === 'embed') { if (this._options.content === 'embed') {
// Sometimes, we can receive a font/media file with textual mime type. Browser // Sometimes, we can receive a font/media file with textual mime type. Browser
// still interprets them correctly, but the 'content-type' header is obviously wrong. // still interprets them correctly, but the 'content-type' header is obviously wrong.
@ -312,18 +348,22 @@ export class HarTracer {
content.encoding = 'base64'; content.encoding = 'base64';
} }
} else if (this._options.content === 'attach') { } else if (this._options.content === 'attach') {
content._sha1 = calculateSha1(buffer) + '.' + (mime.getExtension(content.mimeType) || 'dat'); const sha1 = calculateSha1(buffer) + '.' + (mime.getExtension(content.mimeType) || 'dat');
if (this._options.includeTraceInfo)
content._sha1 = sha1;
else
content._file = sha1;
if (this._started) if (this._started)
this._delegate.onContentBlob(content._sha1, buffer); this._delegate.onContentBlob(sha1, buffer);
} }
} }
private _onResponse(response: network.Response) { private _onResponse(response: network.Response) {
const page = response.frame()._page;
const pageEntry = this._ensurePageEntry(page);
const harEntry = this._entryForRequest(response.request()); const harEntry = this._entryForRequest(response.request());
if (!harEntry) if (!harEntry)
return; return;
const page = response.frame()._page;
const pageEntry = this._createPageEntryIfNeeded(page);
const request = response.request(); const request = response.request();
harEntry.response = { harEntry.response = {
@ -340,16 +380,19 @@ export class HarTracer {
headersSize: -1, headersSize: -1,
bodySize: -1, bodySize: -1,
redirectURL: '', redirectURL: '',
_transferSize: -1 _transferSize: this._options.omitSizes ? undefined : -1
}; };
if (!this._options.omitTiming) {
const timing = response.timing(); const timing = response.timing();
if (pageEntry.startedDateTime.valueOf() > timing.startTime) if (pageEntry && pageEntry.startedDateTime.valueOf() > timing.startTime)
pageEntry.startedDateTime = new Date(timing.startTime); pageEntry.startedDateTime = new Date(timing.startTime);
const dns = timing.domainLookupEnd !== -1 ? helper.millisToRoundishMillis(timing.domainLookupEnd - timing.domainLookupStart) : -1; const dns = timing.domainLookupEnd !== -1 ? helper.millisToRoundishMillis(timing.domainLookupEnd - timing.domainLookupStart) : -1;
const connect = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.connectStart) : -1; const connect = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.connectStart) : -1;
const ssl = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.secureConnectionStart) : -1; const ssl = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.secureConnectionStart) : -1;
const wait = timing.responseStart !== -1 ? helper.millisToRoundishMillis(timing.responseStart - timing.requestStart) : -1; const wait = timing.responseStart !== -1 ? helper.millisToRoundishMillis(timing.responseStart - timing.requestStart) : -1;
const receive = response.request()._responseEndTiming !== -1 ? helper.millisToRoundishMillis(response.request()._responseEndTiming - timing.responseStart) : -1; const receive = response.request()._responseEndTiming !== -1 ? helper.millisToRoundishMillis(response.request()._responseEndTiming - timing.responseStart) : -1;
harEntry.timings = { harEntry.timings = {
dns, dns,
connect, connect,
@ -359,24 +402,34 @@ export class HarTracer {
receive, receive,
}; };
harEntry.time = [dns, connect, ssl, wait, receive].reduce((pre, cur) => cur > 0 ? cur + pre : pre, 0); harEntry.time = [dns, connect, ssl, wait, receive].reduce((pre, cur) => cur > 0 ? cur + pre : pre, 0);
}
if (!this._options.omitServerIP) {
this._addBarrier(page, response.serverAddr().then(server => { this._addBarrier(page, response.serverAddr().then(server => {
if (server?.ipAddress) if (server?.ipAddress)
harEntry.serverIPAddress = server.ipAddress; harEntry.serverIPAddress = server.ipAddress;
if (server?.port) if (server?.port)
harEntry._serverPort = server.port; harEntry._serverPort = server.port;
})); }));
}
if (!this._options.omitSecurityDetails) {
this._addBarrier(page, response.securityDetails().then(details => { this._addBarrier(page, response.securityDetails().then(details => {
if (details) if (details)
harEntry._securityDetails = details; harEntry._securityDetails = details;
})); }));
}
this._addBarrier(page, request.rawRequestHeaders().then(headers => { this._addBarrier(page, request.rawRequestHeaders().then(headers => {
if (!this._options.omitCookies) {
for (const header of headers.filter(header => header.name.toLowerCase() === 'cookie')) for (const header of headers.filter(header => header.name.toLowerCase() === 'cookie'))
harEntry.request.cookies.push(...header.value.split(';').map(parseCookie)); harEntry.request.cookies.push(...header.value.split(';').map(parseCookie));
}
harEntry.request.headers = headers; harEntry.request.headers = headers;
})); }));
this._addBarrier(page, response.rawResponseHeaders().then(headers => { this._addBarrier(page, response.rawResponseHeaders().then(headers => {
if (!this._options.omitCookies) {
for (const header of headers.filter(header => header.name.toLowerCase() === 'set-cookie')) for (const header of headers.filter(header => header.name.toLowerCase() === 'set-cookie'))
harEntry.response.cookies.push(parseCookie(header.value)); harEntry.response.cookies.push(parseCookie(header.value));
}
harEntry.response.headers = headers; harEntry.response.headers = headers;
const contentType = headers.find(header => header.name.toLowerCase() === 'content-type'); const contentType = headers.find(header => header.name.toLowerCase() === 'content-type');
if (contentType) if (contentType)
@ -404,19 +457,21 @@ export class HarTracer {
name: context?._browser.options.name || '', name: context?._browser.options.name || '',
version: context?._browser.version() || '' version: context?._browser.version() || ''
}, },
pages: Array.from(this._pageEntries.values()), pages: this._pageEntries.size ? Array.from(this._pageEntries.values()) : undefined,
entries: [], entries: [],
}; };
for (const pageEntry of log.pages) { if (!this._options.omitTiming) {
if (pageEntry.pageTimings.onContentLoad >= 0) for (const pageEntry of log.pages || []) {
if (typeof pageEntry.pageTimings.onContentLoad === 'number' && pageEntry.pageTimings.onContentLoad >= 0)
pageEntry.pageTimings.onContentLoad -= pageEntry.startedDateTime.valueOf(); pageEntry.pageTimings.onContentLoad -= pageEntry.startedDateTime.valueOf();
else else
pageEntry.pageTimings.onContentLoad = -1; pageEntry.pageTimings.onContentLoad = -1;
if (pageEntry.pageTimings.onLoad >= 0) if (typeof pageEntry.pageTimings.onLoad === 'number' && pageEntry.pageTimings.onLoad >= 0)
pageEntry.pageTimings.onLoad -= pageEntry.startedDateTime.valueOf(); pageEntry.pageTimings.onLoad -= pageEntry.startedDateTime.valueOf();
else else
pageEntry.pageTimings.onLoad = -1; pageEntry.pageTimings.onLoad = -1;
} }
}
this._pageEntries.clear(); this._pageEntries.clear();
return log; return log;
} }
@ -446,8 +501,12 @@ export class HarTracer {
result.text = postData.toString(); result.text = postData.toString();
if (content === 'attach') { if (content === 'attach') {
result._sha1 = calculateSha1(postData) + '.' + (mime.getExtension(contentType) || 'dat'); const sha1 = calculateSha1(postData) + '.' + (mime.getExtension(contentType) || 'dat');
this._delegate.onContentBlob(result._sha1, postData); if (this._options.includeTraceInfo)
result._sha1 = sha1;
else
result._file = sha1;
this._delegate.onContentBlob(sha1, postData);
} }
if (contentType === 'application/x-www-form-urlencoded') { if (contentType === 'application/x-www-form-urlencoded') {
@ -461,11 +520,10 @@ export class HarTracer {
} }
function createHarEntry(method: string, url: URL, requestref: string, frameref: string): har.Entry { function createHarEntry(method: string, url: URL, frameref: string | undefined, options: HarTracerOptions): har.Entry {
const harEntry: har.Entry = { const harEntry: har.Entry = {
_requestref: requestref, _frameref: options.includeTraceInfo ? frameref : undefined,
_frameref: frameref, _monotonicTime: options.includeTraceInfo ? monotonicTime() : undefined,
_monotonicTime: monotonicTime(),
startedDateTime: new Date(), startedDateTime: new Date(),
time: -1, time: -1,
request: { request: {
@ -476,7 +534,7 @@ function createHarEntry(method: string, url: URL, requestref: string, frameref:
headers: [], headers: [],
queryString: [...url.searchParams].map(e => ({ name: e[0], value: e[1] })), queryString: [...url.searchParams].map(e => ({ name: e[0], value: e[1] })),
headersSize: -1, headersSize: -1,
bodySize: 0, bodySize: -1,
}, },
response: { response: {
status: -1, status: -1,
@ -491,12 +549,9 @@ function createHarEntry(method: string, url: URL, requestref: string, frameref:
headersSize: -1, headersSize: -1,
bodySize: -1, bodySize: -1,
redirectURL: '', redirectURL: '',
_transferSize: -1 _transferSize: options.omitSizes ? undefined : -1
},
cache: {
beforeRequest: null,
afterRequest: null,
}, },
cache: {},
timings: { timings: {
send: -1, send: -1,
wait: -1, wait: -1,

View file

@ -244,13 +244,17 @@ export class Route extends SdkObject {
return this._request; return this._request;
} }
async abort(errorCode: string = 'failed', redirectAbortedNavigationToUrl?: string) { async abort(errorCode: string = 'failed') {
this._startHandling(); this._startHandling();
if (redirectAbortedNavigationToUrl)
this._request.frame().redirectNavigationAfterAbort(redirectAbortedNavigationToUrl, this._request._documentId!);
await this._delegate.abort(errorCode); await this._delegate.abort(errorCode);
} }
async redirectNavigationRequest(url: string) {
this._startHandling();
assert(this._request.isNavigationRequest());
this._request.frame().redirectNavigation(url, this._request._documentId!, this._request.headerValue('referer'));
}
async fulfill(overrides: channels.RouteFulfillParams) { async fulfill(overrides: channels.RouteFulfillParams) {
this._startHandling(); this._startHandling();
let body = overrides.body; let body = overrides.body;

View file

@ -88,8 +88,9 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
super(context, 'Tracing'); super(context, 'Tracing');
this._context = context; this._context = context;
this._precreatedTracesDir = tracesDir; this._precreatedTracesDir = tracesDir;
this._harTracer = new HarTracer(context, this, { this._harTracer = new HarTracer(context, null, this, {
content: 'attach', content: 'attach',
includeTraceInfo: true,
waitForContentOnStop: false, waitForContentOnStop: false,
skipScripts: true, skipScripts: true,
}); });

View file

@ -34,7 +34,7 @@ export class InMemorySnapshotter extends BaseSnapshotStorage implements Snapshot
constructor(context: BrowserContext) { constructor(context: BrowserContext) {
super(); super();
this._snapshotter = new Snapshotter(context, this); this._snapshotter = new Snapshotter(context, this);
this._harTracer = new HarTracer(context, this, { content: 'attach', waitForContentOnStop: false, skipScripts: true }); this._harTracer = new HarTracer(context, null, this, { content: 'attach', includeTraceInfo: true, waitForContentOnStop: false, skipScripts: true });
} }
async initialize(): Promise<void> { async initialize(): Promise<void> {

View file

@ -1,167 +0,0 @@
/**
* 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.
*/
// see http://www.softwareishard.com/blog/har-12-spec/
export type HARFile = {
log: HARLog;
}
export type HARLog = {
version: string;
creator: HARCreator;
browser?: HARBrowser;
pages?: HARPage[];
entries: HAREntry[];
comment?: string;
};
export type HARCreator = {
name: string;
version: string;
comment?: string;
};
export type HARBrowser = {
name: string;
version: string;
comment?: string;
};
export type HARPage = {
startedDateTime: string;
id: string;
title: string;
pageTimings: HARPageTimings;
comment?: string;
};
export type HARPageTimings = {
onContentLoad?: number;
onLoad?: number;
comment?: string;
};
export type HAREntry = {
pageref?: string;
startedDateTime: string;
time: number;
request: HARRequest;
response: HARResponse;
cache: HARCache;
timings: HARTimings;
serverIPAddress?: string;
connection?: string;
comment?: string;
};
export type HARRequest = {
method: string;
url: string;
httpVersion: string;
cookies: HARCookie[];
headers: HARHeader[];
queryString: HARQueryParameter[];
postData?: HARPostData;
headersSize: number;
bodySize: number;
comment?: string;
};
export type HARResponse = {
status: number;
statusText: string;
httpVersion: string;
cookies: HARCookie[];
headers: HARHeader[];
content: HARContent;
redirectURL: string;
headersSize: number;
bodySize: number;
comment?: string;
};
export type HARCookie = {
name: string;
value: string;
path?: string;
domain?: string;
expires?: string;
httpOnly?: boolean;
secure?: boolean;
sameSite?: string;
comment?: string;
};
export type HARHeader = {
name: string;
value: string;
comment?: string;
};
export type HARQueryParameter = {
name: string;
value: string;
comment?: string;
};
export type HARPostData = {
mimeType: string;
params: HARParam[];
text: string;
comment?: string;
};
export type HARParam = {
name: string;
value?: string;
fileName?: string;
contentType?: string;
comment?: string;
};
export type HARContent = {
size: number;
compression?: number;
mimeType: string;
text?: string;
encoding?: string;
comment?: string;
};
export type HARCache = {
beforeRequest?: HARCacheState;
afterRequest?: HARCacheState;
comment?: string;
};
export type HARCacheState = {
expires?: string;
lastAccess: string;
eTag: string;
hitCount: number;
comment?: string;
};
export type HARTimings = {
blocked?: number;
dns?: number;
connect?: number;
send: number;
wait: number;
receive: number;
ssl?: number;
comment?: string;
};

View file

@ -810,7 +810,7 @@ CORS RFC1918 enforcement.
export type AttributionReportingIssueType = "PermissionPolicyDisabled"|"AttributionSourceUntrustworthyOrigin"|"AttributionUntrustworthyOrigin"|"InvalidHeader"; export type AttributionReportingIssueType = "PermissionPolicyDisabled"|"AttributionSourceUntrustworthyOrigin"|"AttributionUntrustworthyOrigin"|"InvalidHeader";
/** /**
* Details for issues around "Attribution Reporting API" usage. * Details for issues around "Attribution Reporting API" usage.
Explainer: https://github.com/WICG/conversion-measurement-api Explainer: https://github.com/WICG/attribution-reporting-api
*/ */
export interface AttributionReportingIssueDetails { export interface AttributionReportingIssueDetails {
violationType: AttributionReportingIssueType; violationType: AttributionReportingIssueType;
@ -849,7 +849,7 @@ instead of "limited-quirks".
errorType: GenericIssueErrorType; errorType: GenericIssueErrorType;
frameId?: Page.FrameId; frameId?: Page.FrameId;
} }
export type DeprecationIssueType = "AuthorizationCoveredByWildcard"|"CanRequestURLHTTPContainingNewline"|"ChromeLoadTimesConnectionInfo"|"ChromeLoadTimesFirstPaintAfterLoadTime"|"ChromeLoadTimesWasAlternateProtocolAvailable"|"CookieWithTruncatingChar"|"CrossOriginAccessBasedOnDocumentDomain"|"CrossOriginWindowAlert"|"CrossOriginWindowConfirm"|"CSSSelectorInternalMediaControlsOverlayCastButton"|"CustomCursorIntersectsViewport"|"DeprecationExample"|"DocumentDomainSettingWithoutOriginAgentClusterHeader"|"EventPath"|"GeolocationInsecureOrigin"|"GeolocationInsecureOriginDeprecatedNotRemoved"|"GetUserMediaInsecureOrigin"|"HostCandidateAttributeGetter"|"InsecurePrivateNetworkSubresourceRequest"|"LegacyConstraintGoogIPv6"|"LocalCSSFileExtensionRejected"|"MediaElementAudioSourceNode"|"MediaSourceAbortRemove"|"MediaSourceDurationTruncatingBuffered"|"NoSysexWebMIDIWithoutPermission"|"NotificationInsecureOrigin"|"NotificationPermissionRequestedIframe"|"ObsoleteWebRtcCipherSuite"|"PaymentRequestBasicCard"|"PaymentRequestShowWithoutGesture"|"PictureSourceSrc"|"PrefixedCancelAnimationFrame"|"PrefixedRequestAnimationFrame"|"PrefixedStorageInfo"|"PrefixedVideoDisplayingFullscreen"|"PrefixedVideoEnterFullscreen"|"PrefixedVideoEnterFullScreen"|"PrefixedVideoExitFullscreen"|"PrefixedVideoExitFullScreen"|"PrefixedVideoSupportsFullscreen"|"RangeExpand"|"RequestedSubresourceWithEmbeddedCredentials"|"RTCConstraintEnableDtlsSrtpFalse"|"RTCConstraintEnableDtlsSrtpTrue"|"RTCPeerConnectionComplexPlanBSdpUsingDefaultSdpSemantics"|"RTCPeerConnectionSdpSemanticsPlanB"|"RtcpMuxPolicyNegotiate"|"RTPDataChannel"|"SharedArrayBufferConstructedWithoutIsolation"|"TextToSpeech_DisallowedByAutoplay"|"V8SharedArrayBufferConstructedInExtensionWithoutIsolation"|"XHRJSONEncodingDetection"|"XMLHttpRequestSynchronousInNonWorkerOutsideBeforeUnload"|"XRSupportsSession"; export type DeprecationIssueType = "AuthorizationCoveredByWildcard"|"CanRequestURLHTTPContainingNewline"|"ChromeLoadTimesConnectionInfo"|"ChromeLoadTimesFirstPaintAfterLoadTime"|"ChromeLoadTimesWasAlternateProtocolAvailable"|"CookieWithTruncatingChar"|"CrossOriginAccessBasedOnDocumentDomain"|"CrossOriginWindowAlert"|"CrossOriginWindowConfirm"|"CSSSelectorInternalMediaControlsOverlayCastButton"|"DeprecationExample"|"DocumentDomainSettingWithoutOriginAgentClusterHeader"|"EventPath"|"GeolocationInsecureOrigin"|"GeolocationInsecureOriginDeprecatedNotRemoved"|"GetUserMediaInsecureOrigin"|"HostCandidateAttributeGetter"|"IdentityInCanMakePaymentEvent"|"InsecurePrivateNetworkSubresourceRequest"|"LegacyConstraintGoogIPv6"|"LocalCSSFileExtensionRejected"|"MediaSourceAbortRemove"|"MediaSourceDurationTruncatingBuffered"|"NoSysexWebMIDIWithoutPermission"|"NotificationInsecureOrigin"|"NotificationPermissionRequestedIframe"|"ObsoleteWebRtcCipherSuite"|"OpenWebDatabaseInsecureContext"|"PictureSourceSrc"|"PrefixedCancelAnimationFrame"|"PrefixedRequestAnimationFrame"|"PrefixedStorageInfo"|"PrefixedVideoDisplayingFullscreen"|"PrefixedVideoEnterFullscreen"|"PrefixedVideoEnterFullScreen"|"PrefixedVideoExitFullscreen"|"PrefixedVideoExitFullScreen"|"PrefixedVideoSupportsFullscreen"|"RangeExpand"|"RequestedSubresourceWithEmbeddedCredentials"|"RTCConstraintEnableDtlsSrtpFalse"|"RTCConstraintEnableDtlsSrtpTrue"|"RTCPeerConnectionComplexPlanBSdpUsingDefaultSdpSemantics"|"RTCPeerConnectionSdpSemanticsPlanB"|"RtcpMuxPolicyNegotiate"|"SharedArrayBufferConstructedWithoutIsolation"|"TextToSpeech_DisallowedByAutoplay"|"V8SharedArrayBufferConstructedInExtensionWithoutIsolation"|"XHRJSONEncodingDetection"|"XMLHttpRequestSynchronousInNonWorkerOutsideBeforeUnload"|"XRSupportsSession";
/** /**
* This issue tracks information needed to print a deprecation message. * This issue tracks information needed to print a deprecation message.
https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/third_party/blink/renderer/core/frame/deprecation/README.md https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/third_party/blink/renderer/core/frame/deprecation/README.md
@ -1532,6 +1532,10 @@ inspector" rules), "regular" for regular stylesheets.
* Pseudo element type. * Pseudo element type.
*/ */
pseudoType: DOM.PseudoType; pseudoType: DOM.PseudoType;
/**
* Pseudo element custom ident.
*/
pseudoIdentifier?: string;
/** /**
* Matches of CSS rules applicable to the pseudo style. * Matches of CSS rules applicable to the pseudo style.
*/ */
@ -2068,6 +2072,10 @@ and additional information such as platformFontFamily and fontVariationAxes.
* The font-stretch. * The font-stretch.
*/ */
fontStretch: string; fontStretch: string;
/**
* The font-display.
*/
fontDisplay: string;
/** /**
* The unicode-range. * The unicode-range.
*/ */
@ -2958,6 +2966,11 @@ fire DOM events for nodes known to the client.
* Pseudo element type for this node. * Pseudo element type for this node.
*/ */
pseudoType?: PseudoType; pseudoType?: PseudoType;
/**
* Pseudo element identifier for this node. Only present if there is a
valid pseudoType.
*/
pseudoIdentifier?: string;
/** /**
* Shadow root type. * Shadow root type.
*/ */
@ -2997,6 +3010,7 @@ The property is always undefined now.
*/ */
isSVG?: boolean; isSVG?: boolean;
compatibilityMode?: CompatibilityMode; compatibilityMode?: CompatibilityMode;
assignedSlot?: BackendNode;
} }
/** /**
* A structure holding an RGBA color. * A structure holding an RGBA color.
@ -4706,6 +4720,11 @@ getSnapshot was true.
* Type of a pseudo element node. * Type of a pseudo element node.
*/ */
pseudoType?: RareStringData; pseudoType?: RareStringData;
/**
* Pseudo element identifier for this node. Only present if there is a
valid pseudoType.
*/
pseudoIdentifier?: RareStringData;
/** /**
* Whether this DOM node responds to mouse clicks. This includes nodes that have had click * Whether this DOM node responds to mouse clicks. This includes nodes that have had click
event listeners attached via JavaScript as well as anchor tags that naturally navigate when event listeners attached via JavaScript as well as anchor tags that naturally navigate when
@ -4978,12 +4997,6 @@ The final text color opacity is computed based on the opacity of all overlapping
} }
export type setDOMStorageItemReturnValue = { export type setDOMStorageItemReturnValue = {
} }
export type getStorageKeyForFrameParameters = {
frameId: Page.FrameId;
}
export type getStorageKeyForFrameReturnValue = {
storageKey: SerializedStorageKey;
}
} }
export module Database { export module Database {
@ -5533,6 +5546,14 @@ on Android.
} }
export type setDisabledImageTypesReturnValue = { export type setDisabledImageTypesReturnValue = {
} }
export type setHardwareConcurrencyOverrideParameters = {
/**
* Hardware concurrency to report
*/
hardwareConcurrency: number;
}
export type setHardwareConcurrencyOverrideReturnValue = {
}
/** /**
* Allows overriding user agent with the given string. * Allows overriding user agent with the given string.
*/ */
@ -8063,7 +8084,7 @@ the same request (but not for redirected requests).
initiatorIPAddressSpace: IPAddressSpace; initiatorIPAddressSpace: IPAddressSpace;
privateNetworkRequestPolicy: PrivateNetworkRequestPolicy; privateNetworkRequestPolicy: PrivateNetworkRequestPolicy;
} }
export type CrossOriginOpenerPolicyValue = "SameOrigin"|"SameOriginAllowPopups"|"UnsafeNone"|"SameOriginPlusCoep"|"SameOriginAllowPopupsPlusCoep"; export type CrossOriginOpenerPolicyValue = "SameOrigin"|"SameOriginAllowPopups"|"RestrictProperties"|"UnsafeNone"|"SameOriginPlusCoep"|"RestrictPropertiesPlusCoep";
export interface CrossOriginOpenerPolicyStatus { export interface CrossOriginOpenerPolicyStatus {
value: CrossOriginOpenerPolicyValue; value: CrossOriginOpenerPolicyValue;
reportOnlyValue: CrossOriginOpenerPolicyValue; reportOnlyValue: CrossOriginOpenerPolicyValue;
@ -10166,6 +10187,21 @@ Backend then generates 'inspectNodeRequested' event upon element selection.
adFrameType: AdFrameType; adFrameType: AdFrameType;
explanations?: AdFrameExplanation[]; explanations?: AdFrameExplanation[];
} }
/**
* Identifies the bottom-most script which caused the frame to be labelled
as an ad.
*/
export interface AdScriptId {
/**
* Script Id of the bottom-most script which caused the frame to be labelled
as an ad.
*/
scriptId: Runtime.ScriptId;
/**
* Id of adScriptId's debugger.
*/
debuggerId: Runtime.UniqueDebuggerId;
}
/** /**
* Indicates whether the frame is a secure context and why it is the case. * Indicates whether the frame is a secure context and why it is the case.
*/ */
@ -10179,7 +10215,7 @@ Backend then generates 'inspectNodeRequested' event upon element selection.
* 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"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"browsing-topics"|"camera"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-full"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-reduced"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"cross-origin-isolated"|"direct-sockets"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"storage-access-api"|"sync-xhr"|"trust-token-redemption"|"usb"|"vertical-scroll"|"web-share"|"window-placement"|"xr-spatial-tracking"; export type PermissionsPolicyFeature = "accelerometer"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-full"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-reduced"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"cross-origin-isolated"|"direct-sockets"|"display-capture"|"document-domain"|"encrypted-media"|"execution-while-out-of-viewport"|"execution-while-not-rendered"|"focus-without-user-activation"|"fullscreen"|"frobulate"|"gamepad"|"geolocation"|"gyroscope"|"hid"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"storage-access-api"|"sync-xhr"|"trust-token-redemption"|"usb"|"vertical-scroll"|"web-share"|"window-placement"|"xr-spatial-tracking";
/** /**
* Reason for a permissions policy feature to be disabled. * Reason for a permissions policy feature to be disabled.
*/ */
@ -10644,7 +10680,7 @@ Example URLs: http://www.google.com/file.html -> "google.com"
/** /**
* List of not restored reasons for back-forward cache. * List of not restored reasons for back-forward cache.
*/ */
export type BackForwardCacheNotRestoredReason = "NotPrimaryMainFrame"|"BackForwardCacheDisabled"|"RelatedActiveContentsExist"|"HTTPStatusNotOK"|"SchemeNotHTTPOrHTTPS"|"Loading"|"WasGrantedMediaAccess"|"DisableForRenderFrameHostCalled"|"DomainNotAllowed"|"HTTPMethodNotGET"|"SubframeIsNavigating"|"Timeout"|"CacheLimit"|"JavaScriptExecution"|"RendererProcessKilled"|"RendererProcessCrashed"|"SchedulerTrackedFeatureUsed"|"ConflictingBrowsingInstance"|"CacheFlushed"|"ServiceWorkerVersionActivation"|"SessionRestored"|"ServiceWorkerPostMessage"|"EnteredBackForwardCacheBeforeServiceWorkerHostAdded"|"RenderFrameHostReused_SameSite"|"RenderFrameHostReused_CrossSite"|"ServiceWorkerClaim"|"IgnoreEventAndEvict"|"HaveInnerContents"|"TimeoutPuttingInCache"|"BackForwardCacheDisabledByLowMemory"|"BackForwardCacheDisabledByCommandLine"|"NetworkRequestDatapipeDrainedAsBytesConsumer"|"NetworkRequestRedirected"|"NetworkRequestTimeout"|"NetworkExceedsBufferLimit"|"NavigationCancelledWhileRestoring"|"NotMostRecentNavigationEntry"|"BackForwardCacheDisabledForPrerender"|"UserAgentOverrideDiffers"|"ForegroundCacheLimit"|"BrowsingInstanceNotSwapped"|"BackForwardCacheDisabledForDelegate"|"UnloadHandlerExistsInMainFrame"|"UnloadHandlerExistsInSubFrame"|"ServiceWorkerUnregistration"|"CacheControlNoStore"|"CacheControlNoStoreCookieModified"|"CacheControlNoStoreHTTPOnlyCookieModified"|"NoResponseHead"|"Unknown"|"ActivationNavigationsDisallowedForBug1234857"|"ErrorDocument"|"FencedFramesEmbedder"|"WebSocket"|"WebTransport"|"WebRTC"|"MainResourceHasCacheControlNoStore"|"MainResourceHasCacheControlNoCache"|"SubresourceHasCacheControlNoStore"|"SubresourceHasCacheControlNoCache"|"ContainsPlugins"|"DocumentLoaded"|"DedicatedWorkerOrWorklet"|"OutstandingNetworkRequestOthers"|"OutstandingIndexedDBTransaction"|"RequestedNotificationsPermission"|"RequestedMIDIPermission"|"RequestedAudioCapturePermission"|"RequestedVideoCapturePermission"|"RequestedBackForwardCacheBlockedSensors"|"RequestedBackgroundWorkPermission"|"BroadcastChannel"|"IndexedDBConnection"|"WebXR"|"SharedWorker"|"WebLocks"|"WebHID"|"WebShare"|"RequestedStorageAccessGrant"|"WebNfc"|"OutstandingNetworkRequestFetch"|"OutstandingNetworkRequestXHR"|"AppBanner"|"Printing"|"WebDatabase"|"PictureInPicture"|"Portal"|"SpeechRecognizer"|"IdleManager"|"PaymentManager"|"SpeechSynthesis"|"KeyboardLock"|"WebOTPService"|"OutstandingNetworkRequestDirectSocket"|"InjectedJavascript"|"InjectedStyleSheet"|"Dummy"|"ContentSecurityHandler"|"ContentWebAuthenticationAPI"|"ContentFileChooser"|"ContentSerial"|"ContentFileSystemAccess"|"ContentMediaDevicesDispatcherHost"|"ContentWebBluetooth"|"ContentWebUSB"|"ContentMediaSession"|"ContentMediaSessionService"|"ContentScreenReader"|"EmbedderPopupBlockerTabHelper"|"EmbedderSafeBrowsingTriggeredPopupBlocker"|"EmbedderSafeBrowsingThreatDetails"|"EmbedderAppBannerManager"|"EmbedderDomDistillerViewerSource"|"EmbedderDomDistillerSelfDeletingRequestDelegate"|"EmbedderOomInterventionTabHelper"|"EmbedderOfflinePage"|"EmbedderChromePasswordManagerClientBindCredentialManager"|"EmbedderPermissionRequestManager"|"EmbedderModalDialog"|"EmbedderExtensions"|"EmbedderExtensionMessaging"|"EmbedderExtensionMessagingForOpenPort"|"EmbedderExtensionSentMessageToCachedFrame"; export type BackForwardCacheNotRestoredReason = "NotPrimaryMainFrame"|"BackForwardCacheDisabled"|"RelatedActiveContentsExist"|"HTTPStatusNotOK"|"SchemeNotHTTPOrHTTPS"|"Loading"|"WasGrantedMediaAccess"|"DisableForRenderFrameHostCalled"|"DomainNotAllowed"|"HTTPMethodNotGET"|"SubframeIsNavigating"|"Timeout"|"CacheLimit"|"JavaScriptExecution"|"RendererProcessKilled"|"RendererProcessCrashed"|"SchedulerTrackedFeatureUsed"|"ConflictingBrowsingInstance"|"CacheFlushed"|"ServiceWorkerVersionActivation"|"SessionRestored"|"ServiceWorkerPostMessage"|"EnteredBackForwardCacheBeforeServiceWorkerHostAdded"|"RenderFrameHostReused_SameSite"|"RenderFrameHostReused_CrossSite"|"ServiceWorkerClaim"|"IgnoreEventAndEvict"|"HaveInnerContents"|"TimeoutPuttingInCache"|"BackForwardCacheDisabledByLowMemory"|"BackForwardCacheDisabledByCommandLine"|"NetworkRequestDatapipeDrainedAsBytesConsumer"|"NetworkRequestRedirected"|"NetworkRequestTimeout"|"NetworkExceedsBufferLimit"|"NavigationCancelledWhileRestoring"|"NotMostRecentNavigationEntry"|"BackForwardCacheDisabledForPrerender"|"UserAgentOverrideDiffers"|"ForegroundCacheLimit"|"BrowsingInstanceNotSwapped"|"BackForwardCacheDisabledForDelegate"|"UnloadHandlerExistsInMainFrame"|"UnloadHandlerExistsInSubFrame"|"ServiceWorkerUnregistration"|"CacheControlNoStore"|"CacheControlNoStoreCookieModified"|"CacheControlNoStoreHTTPOnlyCookieModified"|"NoResponseHead"|"Unknown"|"ActivationNavigationsDisallowedForBug1234857"|"ErrorDocument"|"FencedFramesEmbedder"|"WebSocket"|"WebTransport"|"WebRTC"|"MainResourceHasCacheControlNoStore"|"MainResourceHasCacheControlNoCache"|"SubresourceHasCacheControlNoStore"|"SubresourceHasCacheControlNoCache"|"ContainsPlugins"|"DocumentLoaded"|"DedicatedWorkerOrWorklet"|"OutstandingNetworkRequestOthers"|"OutstandingIndexedDBTransaction"|"RequestedNotificationsPermission"|"RequestedMIDIPermission"|"RequestedAudioCapturePermission"|"RequestedVideoCapturePermission"|"RequestedBackForwardCacheBlockedSensors"|"RequestedBackgroundWorkPermission"|"BroadcastChannel"|"IndexedDBConnection"|"WebXR"|"SharedWorker"|"WebLocks"|"WebHID"|"WebShare"|"RequestedStorageAccessGrant"|"WebNfc"|"OutstandingNetworkRequestFetch"|"OutstandingNetworkRequestXHR"|"AppBanner"|"Printing"|"WebDatabase"|"PictureInPicture"|"Portal"|"SpeechRecognizer"|"IdleManager"|"PaymentManager"|"SpeechSynthesis"|"KeyboardLock"|"WebOTPService"|"OutstandingNetworkRequestDirectSocket"|"InjectedJavascript"|"InjectedStyleSheet"|"Dummy"|"ContentSecurityHandler"|"ContentWebAuthenticationAPI"|"ContentFileChooser"|"ContentSerial"|"ContentFileSystemAccess"|"ContentMediaDevicesDispatcherHost"|"ContentWebBluetooth"|"ContentWebUSB"|"ContentMediaSessionService"|"ContentScreenReader"|"EmbedderPopupBlockerTabHelper"|"EmbedderSafeBrowsingTriggeredPopupBlocker"|"EmbedderSafeBrowsingThreatDetails"|"EmbedderAppBannerManager"|"EmbedderDomDistillerViewerSource"|"EmbedderDomDistillerSelfDeletingRequestDelegate"|"EmbedderOomInterventionTabHelper"|"EmbedderOfflinePage"|"EmbedderChromePasswordManagerClientBindCredentialManager"|"EmbedderPermissionRequestManager"|"EmbedderModalDialog"|"EmbedderExtensions"|"EmbedderExtensionMessaging"|"EmbedderExtensionMessagingForOpenPort"|"EmbedderExtensionSentMessageToCachedFrame";
/** /**
* Types of not restored reasons for back-forward cache. * Types of not restored reasons for back-forward cache.
*/ */
@ -10720,6 +10756,11 @@ dependent on the reason:
* JavaScript stack trace of when frame was attached, only set if frame initiated from script. * JavaScript stack trace of when frame was attached, only set if frame initiated from script.
*/ */
stack?: Runtime.StackTrace; stack?: Runtime.StackTrace;
/**
* Identifies the bottom-most script which caused the frame to be labelled
as an ad. Only sent if frame is labelled as an ad and id is available.
*/
adScriptId?: AdScriptId;
} }
/** /**
* Fired when frame no longer has a scheduled navigation. * Fired when frame no longer has a scheduled navigation.
@ -12668,6 +12709,15 @@ Tokens from that issuer.
name: string; name: string;
} }
/**
* Returns a storage key given a frame id.
*/
export type getStorageKeyForFrameParameters = {
frameId: Page.FrameId;
}
export type getStorageKeyForFrameReturnValue = {
storageKey: SerializedStorageKey;
}
/** /**
* Clears storage for origin. * Clears storage for origin.
*/ */
@ -15248,13 +15298,30 @@ of scripts is used as end of range.
export type removeBreakpointReturnValue = { export type removeBreakpointReturnValue = {
} }
/** /**
* Restarts particular call frame from the beginning. * Restarts particular call frame from the beginning. The old, deprecated
behavior of `restartFrame` is to stay paused and allow further CDP commands
after a restart was scheduled. This can cause problems with restarting, so
we now continue execution immediatly after it has been scheduled until we
reach the beginning of the restarted frame.
To stay back-wards compatible, `restartFrame` now expects a `mode`
parameter to be present. If the `mode` parameter is missing, `restartFrame`
errors out.
The various return values are deprecated and `callFrames` is always empty.
Use the call frames from the `Debugger#paused` events instead, that fires
once V8 pauses at the beginning of the restarted function.
*/ */
export type restartFrameParameters = { export type restartFrameParameters = {
/** /**
* Call frame identifier to evaluate on. * Call frame identifier to evaluate on.
*/ */
callFrameId: CallFrameId; callFrameId: CallFrameId;
/**
* The `mode` parameter must be present and set to 'StepInto', otherwise
`restartFrame` will error out.
*/
mode?: "StepInto";
} }
export type restartFrameReturnValue = { export type restartFrameReturnValue = {
/** /**
@ -17514,7 +17581,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;
"DOMStorage.getStorageKeyForFrame": DOMStorage.getStorageKeyForFrameParameters;
"Database.disable": Database.disableParameters; "Database.disable": Database.disableParameters;
"Database.enable": Database.enableParameters; "Database.enable": Database.enableParameters;
"Database.executeSQL": Database.executeSQLParameters; "Database.executeSQL": Database.executeSQLParameters;
@ -17547,6 +17613,7 @@ Error was thrown.
"Emulation.setTimezoneOverride": Emulation.setTimezoneOverrideParameters; "Emulation.setTimezoneOverride": Emulation.setTimezoneOverrideParameters;
"Emulation.setVisibleSize": Emulation.setVisibleSizeParameters; "Emulation.setVisibleSize": Emulation.setVisibleSizeParameters;
"Emulation.setDisabledImageTypes": Emulation.setDisabledImageTypesParameters; "Emulation.setDisabledImageTypes": Emulation.setDisabledImageTypesParameters;
"Emulation.setHardwareConcurrencyOverride": Emulation.setHardwareConcurrencyOverrideParameters;
"Emulation.setUserAgentOverride": Emulation.setUserAgentOverrideParameters; "Emulation.setUserAgentOverride": Emulation.setUserAgentOverrideParameters;
"Emulation.setAutomationOverride": Emulation.setAutomationOverrideParameters; "Emulation.setAutomationOverride": Emulation.setAutomationOverrideParameters;
"HeadlessExperimental.beginFrame": HeadlessExperimental.beginFrameParameters; "HeadlessExperimental.beginFrame": HeadlessExperimental.beginFrameParameters;
@ -17744,6 +17811,7 @@ Error was thrown.
"ServiceWorker.stopWorker": ServiceWorker.stopWorkerParameters; "ServiceWorker.stopWorker": ServiceWorker.stopWorkerParameters;
"ServiceWorker.unregister": ServiceWorker.unregisterParameters; "ServiceWorker.unregister": ServiceWorker.unregisterParameters;
"ServiceWorker.updateRegistration": ServiceWorker.updateRegistrationParameters; "ServiceWorker.updateRegistration": ServiceWorker.updateRegistrationParameters;
"Storage.getStorageKeyForFrame": Storage.getStorageKeyForFrameParameters;
"Storage.clearDataForOrigin": Storage.clearDataForOriginParameters; "Storage.clearDataForOrigin": Storage.clearDataForOriginParameters;
"Storage.getCookies": Storage.getCookiesParameters; "Storage.getCookies": Storage.getCookiesParameters;
"Storage.setCookies": Storage.setCookiesParameters; "Storage.setCookies": Storage.setCookiesParameters;
@ -18044,7 +18112,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;
"DOMStorage.getStorageKeyForFrame": DOMStorage.getStorageKeyForFrameReturnValue;
"Database.disable": Database.disableReturnValue; "Database.disable": Database.disableReturnValue;
"Database.enable": Database.enableReturnValue; "Database.enable": Database.enableReturnValue;
"Database.executeSQL": Database.executeSQLReturnValue; "Database.executeSQL": Database.executeSQLReturnValue;
@ -18077,6 +18144,7 @@ Error was thrown.
"Emulation.setTimezoneOverride": Emulation.setTimezoneOverrideReturnValue; "Emulation.setTimezoneOverride": Emulation.setTimezoneOverrideReturnValue;
"Emulation.setVisibleSize": Emulation.setVisibleSizeReturnValue; "Emulation.setVisibleSize": Emulation.setVisibleSizeReturnValue;
"Emulation.setDisabledImageTypes": Emulation.setDisabledImageTypesReturnValue; "Emulation.setDisabledImageTypes": Emulation.setDisabledImageTypesReturnValue;
"Emulation.setHardwareConcurrencyOverride": Emulation.setHardwareConcurrencyOverrideReturnValue;
"Emulation.setUserAgentOverride": Emulation.setUserAgentOverrideReturnValue; "Emulation.setUserAgentOverride": Emulation.setUserAgentOverrideReturnValue;
"Emulation.setAutomationOverride": Emulation.setAutomationOverrideReturnValue; "Emulation.setAutomationOverride": Emulation.setAutomationOverrideReturnValue;
"HeadlessExperimental.beginFrame": HeadlessExperimental.beginFrameReturnValue; "HeadlessExperimental.beginFrame": HeadlessExperimental.beginFrameReturnValue;
@ -18274,6 +18342,7 @@ Error was thrown.
"ServiceWorker.stopWorker": ServiceWorker.stopWorkerReturnValue; "ServiceWorker.stopWorker": ServiceWorker.stopWorkerReturnValue;
"ServiceWorker.unregister": ServiceWorker.unregisterReturnValue; "ServiceWorker.unregister": ServiceWorker.unregisterReturnValue;
"ServiceWorker.updateRegistration": ServiceWorker.updateRegistrationReturnValue; "ServiceWorker.updateRegistration": ServiceWorker.updateRegistrationReturnValue;
"Storage.getStorageKeyForFrame": Storage.getStorageKeyForFrameReturnValue;
"Storage.clearDataForOrigin": Storage.clearDataForOriginReturnValue; "Storage.clearDataForOrigin": Storage.clearDataForOriginReturnValue;
"Storage.getCookies": Storage.getCookiesReturnValue; "Storage.getCookies": Storage.getCookiesReturnValue;
"Storage.setCookies": Storage.setCookiesReturnValue; "Storage.setCookies": Storage.setCookiesReturnValue;

View file

@ -17,7 +17,6 @@
import { Protocol } from 'playwright-core/types/protocol'; import { Protocol } from 'playwright-core/types/protocol';
import { ChildProcess } from 'child_process'; import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { HARResponse } from 'playwright-core/types/har';
import { Readable } from 'stream'; import { Readable } from 'stream';
import { ReadStream } from 'fs'; import { ReadStream } from 'fs';
import { Serializable, EvaluationArgument, PageFunction, PageFunctionOn, SmartHandle, ElementHandleForTag, BindingSource } from 'playwright-core/types/structs'; import { Serializable, EvaluationArgument, PageFunction, PageFunctionOn, SmartHandle, ElementHandleForTag, BindingSource } from 'playwright-core/types/structs';
@ -3167,6 +3166,37 @@ export interface Page {
times?: number; times?: number;
}): Promise<void>; }): Promise<void>;
/**
* If specified the network requests that are made in the page will be served from the HAR file. Read more about
* [Replaying from HAR](https://playwright.dev/docs/network#replaying-from-har).
*
* Playwright will not serve requests intercepted by Service Worker from the HAR file. See
* [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend disabling Service Workers when using
* request interception by setting `Browser.newContext.serviceWorkers` to `'block'`.
* @param har Path to a [HAR](http://www.softwareishard.com/blog/har-12-spec) file with prerecorded network data. If `path` is a relative path, then it is resolved relative to the current working directory.
* @param options
*/
routeFromHAR(har: string, options?: {
/**
* - If set to 'abort' any request not found in the HAR file will be aborted.
* - If set to 'fallback' missing requests will be sent to the network.
*
* Defaults to abort.
*/
notFound?: "abort"|"fallback";
/**
* If specified, updates the given HAR with the actual network information instead of serving from file.
*/
update?: boolean;
/**
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
* will be surved from the HAR file. If not specified, all requests are served from the HAR file.
*/
url?: string|RegExp;
}): Promise<void>;
/** /**
* Returns the buffer with the captured screenshot. * Returns the buffer with the captured screenshot.
* @param options * @param options
@ -7093,6 +7123,37 @@ export interface BrowserContext {
times?: number; times?: number;
}): Promise<void>; }): Promise<void>;
/**
* If specified the network requests that are made in the context will be served from the HAR file. Read more about
* [Replaying from HAR](https://playwright.dev/docs/network#replaying-from-har).
*
* Playwright will not serve requests intercepted by Service Worker from the HAR file. See
* [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend disabling Service Workers when using
* request interception by setting `Browser.newContext.serviceWorkers` to `'block'`.
* @param har Path to a [HAR](http://www.softwareishard.com/blog/har-12-spec) file with prerecorded network data. If `path` is a relative path, then it is resolved relative to the current working directory.
* @param options
*/
routeFromHAR(har: string, options?: {
/**
* - If set to 'abort' any request not found in the HAR file will be aborted.
* - If set to 'fallback' falls through to the next route handler in the handler chain.
*
* Defaults to abort.
*/
notFound?: "abort"|"fallback";
/**
* If specified, updates the given HAR with the actual network information instead of serving from file.
*/
update?: boolean;
/**
* A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
* will be surved from the HAR file. If not specified, all requests are served from the HAR file.
*/
url?: string|RegExp;
}): Promise<void>;
/** /**
* > NOTE: Service workers are only supported on Chromium-based browsers. * > NOTE: Service workers are only supported on Chromium-based browsers.
* *
@ -10507,34 +10568,6 @@ export interface BrowserType<Unused = {}> {
*/ */
handleSIGTERM?: boolean; handleSIGTERM?: boolean;
/**
* If specified the network requests that are made in the context will be served from the HAR file. Read more about
* [Replaying from HAR](https://playwright.dev/docs/network#replaying-from-har).
*
* > NOTE: Playwright will not serve requests intercepted by Service Worker from the HAR file. See
* [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend disabling Service Workers when using
* request interception by setting `Browser.newContext.serviceWorkers` to `'block'`.
*/
har?: {
/**
* Path to a [HAR](http://www.softwareishard.com/blog/har-12-spec) file with prerecorded network data. If `path` is a
* relative path, then it is resolved relative to the current working directory.
*/
path: string;
/**
* If set to 'abort' any request not found in the HAR file will be aborted. If set to'continue' missing requests will be
* sent to the network. Defaults to 'abort'.
*/
fallback?: "abort"|"continue";
/**
* A glob pattern or regular expression to match request URL while routing. Only requests with URL matching the pattern
* will be surved from the HAR file. If not specified, all requests are served from the HAR file.
*/
urlFilter?: string|RegExp;
};
/** /**
* Specifies if viewport supports touch events. Defaults to false. * Specifies if viewport supports touch events. Defaults to false.
*/ */
@ -10643,16 +10676,24 @@ export interface BrowserType<Unused = {}> {
/** /**
* Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` * Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach`
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. * is specified, resources are persistet as separate files or entries in the ZIP archive. If `embed` is specified, content
* Defaults to `embed`, which stores content inline the HAR file as per HAR specification. * is stored inline the HAR file as per HAR specification. Defaults to `attach` for `.zip` output files and to `embed` for
* all other file extensions.
*/ */
content?: "omit"|"embed"|"attach"; content?: "omit"|"embed"|"attach";
/** /**
* Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `attach` mode is used by default. * Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `content: 'attach'` is used by
* default.
*/ */
path: string; path: string;
/**
* When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
* security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`.
*/
mode?: "full"|"minimal";
/** /**
* A glob or regex pattern to filter requests that are stored in the HAR. When a `baseURL` via the context options was * A glob or regex pattern to filter requests that are stored in the HAR. When a `baseURL` via the context options was
* provided and the passed URL is a path, it gets merged via the * provided and the passed URL is a path, it gets merged via the
@ -11762,34 +11803,6 @@ export interface AndroidDevice {
accuracy?: number; accuracy?: number;
}; };
/**
* If specified the network requests that are made in the context will be served from the HAR file. Read more about
* [Replaying from HAR](https://playwright.dev/docs/network#replaying-from-har).
*
* > NOTE: Playwright will not serve requests intercepted by Service Worker from the HAR file. See
* [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend disabling Service Workers when using
* request interception by setting `Browser.newContext.serviceWorkers` to `'block'`.
*/
har?: {
/**
* Path to a [HAR](http://www.softwareishard.com/blog/har-12-spec) file with prerecorded network data. If `path` is a
* relative path, then it is resolved relative to the current working directory.
*/
path: string;
/**
* If set to 'abort' any request not found in the HAR file will be aborted. If set to'continue' missing requests will be
* sent to the network. Defaults to 'abort'.
*/
fallback?: "abort"|"continue";
/**
* A glob pattern or regular expression to match request URL while routing. Only requests with URL matching the pattern
* will be surved from the HAR file. If not specified, all requests are served from the HAR file.
*/
urlFilter?: string|RegExp;
};
/** /**
* Specifies if viewport supports touch events. Defaults to false. * Specifies if viewport supports touch events. Defaults to false.
*/ */
@ -11858,16 +11871,24 @@ export interface AndroidDevice {
/** /**
* Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` * Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach`
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. * is specified, resources are persistet as separate files or entries in the ZIP archive. If `embed` is specified, content
* Defaults to `embed`, which stores content inline the HAR file as per HAR specification. * is stored inline the HAR file as per HAR specification. Defaults to `attach` for `.zip` output files and to `embed` for
* all other file extensions.
*/ */
content?: "omit"|"embed"|"attach"; content?: "omit"|"embed"|"attach";
/** /**
* Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `attach` mode is used by default. * Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `content: 'attach'` is used by
* default.
*/ */
path: string; path: string;
/**
* When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
* security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`.
*/
mode?: "full"|"minimal";
/** /**
* A glob or regex pattern to filter requests that are stored in the HAR. When a `baseURL` via the context options was * A glob or regex pattern to filter requests that are stored in the HAR. When a `baseURL` via the context options was
* provided and the passed URL is a path, it gets merged via the * provided and the passed URL is a path, it gets merged via the
@ -13330,34 +13351,6 @@ export interface Browser extends EventEmitter {
accuracy?: number; accuracy?: number;
}; };
/**
* If specified the network requests that are made in the context will be served from the HAR file. Read more about
* [Replaying from HAR](https://playwright.dev/docs/network#replaying-from-har).
*
* > NOTE: Playwright will not serve requests intercepted by Service Worker from the HAR file. See
* [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend disabling Service Workers when using
* request interception by setting `Browser.newContext.serviceWorkers` to `'block'`.
*/
har?: {
/**
* Path to a [HAR](http://www.softwareishard.com/blog/har-12-spec) file with prerecorded network data. If `path` is a
* relative path, then it is resolved relative to the current working directory.
*/
path: string;
/**
* If set to 'abort' any request not found in the HAR file will be aborted. If set to'continue' missing requests will be
* sent to the network. Defaults to 'abort'.
*/
fallback?: "abort"|"continue";
/**
* A glob pattern or regular expression to match request URL while routing. Only requests with URL matching the pattern
* will be surved from the HAR file. If not specified, all requests are served from the HAR file.
*/
urlFilter?: string|RegExp;
};
/** /**
* Specifies if viewport supports touch events. Defaults to false. * Specifies if viewport supports touch events. Defaults to false.
*/ */
@ -13456,16 +13449,24 @@ export interface Browser extends EventEmitter {
/** /**
* Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` * Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach`
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. * is specified, resources are persistet as separate files or entries in the ZIP archive. If `embed` is specified, content
* Defaults to `embed`, which stores content inline the HAR file as per HAR specification. * is stored inline the HAR file as per HAR specification. Defaults to `attach` for `.zip` output files and to `embed` for
* all other file extensions.
*/ */
content?: "omit"|"embed"|"attach"; content?: "omit"|"embed"|"attach";
/** /**
* Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `attach` mode is used by default. * Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `content: 'attach'` is used by
* default.
*/ */
path: string; path: string;
/**
* When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
* security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`.
*/
mode?: "full"|"minimal";
/** /**
* A glob or regex pattern to filter requests that are stored in the HAR. When a `baseURL` via the context options was * A glob or regex pattern to filter requests that are stored in the HAR. When a `baseURL` via the context options was
* provided and the passed URL is a path, it gets merged via the * provided and the passed URL is a path, it gets merged via the
@ -14202,34 +14203,6 @@ export interface Electron {
accuracy?: number; accuracy?: number;
}; };
/**
* If specified the network requests that are made in the context will be served from the HAR file. Read more about
* [Replaying from HAR](https://playwright.dev/docs/network#replaying-from-har).
*
* > NOTE: Playwright will not serve requests intercepted by Service Worker from the HAR file. See
* [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend disabling Service Workers when using
* request interception by setting `Browser.newContext.serviceWorkers` to `'block'`.
*/
har?: {
/**
* Path to a [HAR](http://www.softwareishard.com/blog/har-12-spec) file with prerecorded network data. If `path` is a
* relative path, then it is resolved relative to the current working directory.
*/
path: string;
/**
* If set to 'abort' any request not found in the HAR file will be aborted. If set to'continue' missing requests will be
* sent to the network. Defaults to 'abort'.
*/
fallback?: "abort"|"continue";
/**
* A glob pattern or regular expression to match request URL while routing. Only requests with URL matching the pattern
* will be surved from the HAR file. If not specified, all requests are served from the HAR file.
*/
urlFilter?: string|RegExp;
};
/** /**
* Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication). * Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
*/ */
@ -14270,16 +14243,24 @@ export interface Electron {
/** /**
* Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` * Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach`
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. * is specified, resources are persistet as separate files or entries in the ZIP archive. If `embed` is specified, content
* Defaults to `embed`, which stores content inline the HAR file as per HAR specification. * is stored inline the HAR file as per HAR specification. Defaults to `attach` for `.zip` output files and to `embed` for
* all other file extensions.
*/ */
content?: "omit"|"embed"|"attach"; content?: "omit"|"embed"|"attach";
/** /**
* Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `attach` mode is used by default. * Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `content: 'attach'` is used by
* default.
*/ */
path: string; path: string;
/**
* When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
* security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`.
*/
mode?: "full"|"minimal";
/** /**
* A glob or regex pattern to filter requests that are stored in the HAR. When a `baseURL` via the context options was * A glob or regex pattern to filter requests that are stored in the HAR. When a `baseURL` via the context options was
* provided and the passed URL is a path, it gets merged via the * provided and the passed URL is a path, it gets merged via the
@ -14895,9 +14876,10 @@ export interface Request {
frame(): Frame; frame(): Frame;
/** /**
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use * An object with the request HTTP headers. The header names are lower-cased. Note that this method does not return
* [request.allHeaders()](https://playwright.dev/docs/api/class-request#request-all-headers) instead. * security-related headers, including cookie-related ones. You can use
* @deprecated * [request.allHeaders()](https://playwright.dev/docs/api/class-request#request-all-headers) for complete list of headers
* that include `cookie` information.
*/ */
headers(): { [key: string]: string; }; headers(): { [key: string]: string; };
@ -15132,9 +15114,10 @@ export interface Response {
fromServiceWorker(): boolean; fromServiceWorker(): boolean;
/** /**
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use * An object with the response HTTP headers. The header names are lower-cased. Note that this method does not return
* [response.allHeaders()](https://playwright.dev/docs/api/class-response#response-all-headers) instead. * security-related headers, including cookie-related ones. You can use
* @deprecated * [response.allHeaders()](https://playwright.dev/docs/api/class-response#response-all-headers) for complete list of
* headers that include `cookie` information.
*/ */
headers(): { [key: string]: string; }; headers(): { [key: string]: string; };
@ -15995,34 +15978,6 @@ export interface BrowserContextOptions {
geolocation?: Geolocation; geolocation?: Geolocation;
/**
* If specified the network requests that are made in the context will be served from the HAR file. Read more about
* [Replaying from HAR](https://playwright.dev/docs/network#replaying-from-har).
*
* > NOTE: Playwright will not serve requests intercepted by Service Worker from the HAR file. See
* [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend disabling Service Workers when using
* request interception by setting `Browser.newContext.serviceWorkers` to `'block'`.
*/
har?: {
/**
* Path to a [HAR](http://www.softwareishard.com/blog/har-12-spec) file with prerecorded network data. If `path` is a
* relative path, then it is resolved relative to the current working directory.
*/
path: string;
/**
* If set to 'abort' any request not found in the HAR file will be aborted. If set to'continue' missing requests will be
* sent to the network. Defaults to 'abort'.
*/
fallback?: "abort"|"continue";
/**
* A glob pattern or regular expression to match request URL while routing. Only requests with URL matching the pattern
* will be surved from the HAR file. If not specified, all requests are served from the HAR file.
*/
urlFilter?: string|RegExp;
};
/** /**
* Specifies if viewport supports touch events. Defaults to false. * Specifies if viewport supports touch events. Defaults to false.
*/ */
@ -16117,16 +16072,24 @@ export interface BrowserContextOptions {
/** /**
* Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` * Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach`
* is specified, resources are persistet as separate files and all of these files are archived along with the HAR file. * is specified, resources are persistet as separate files or entries in the ZIP archive. If `embed` is specified, content
* Defaults to `embed`, which stores content inline the HAR file as per HAR specification. * is stored inline the HAR file as per HAR specification. Defaults to `attach` for `.zip` output files and to `embed` for
* all other file extensions.
*/ */
content?: "omit"|"embed"|"attach"; content?: "omit"|"embed"|"attach";
/** /**
* Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `attach` mode is used by default. * Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `content: 'attach'` is used by
* default.
*/ */
path: string; path: string;
/**
* When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
* security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`.
*/
mode?: "full"|"minimal";
/** /**
* A glob or regex pattern to filter requests that are stored in the HAR. When a `baseURL` via the context options was * A glob or regex pattern to filter requests that are stored in the HAR. When a `baseURL` via the context options was
* provided and the passed URL is a path, it gets merged via the * provided and the passed URL is a path, it gets merged via the

View file

@ -1,6 +1,6 @@
{ {
"name": "@playwright/experimental-ct-react", "name": "@playwright/experimental-ct-react",
"version": "1.23.0-next", "version": "1.23.4",
"description": "Playwright Component Testing for React", "description": "Playwright Component Testing for React",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -23,7 +23,7 @@
}, },
"dependencies": { "dependencies": {
"@vitejs/plugin-react": "^1.0.7", "@vitejs/plugin-react": "^1.0.7",
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.4",
"vite": "^2.9.5" "vite": "^2.9.5"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@playwright/experimental-ct-svelte", "name": "@playwright/experimental-ct-svelte",
"version": "1.23.0-next", "version": "1.23.4",
"description": "Playwright Component Testing for Svelte", "description": "Playwright Component Testing for Svelte",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -23,7 +23,7 @@
}, },
"dependencies": { "dependencies": {
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.30", "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.4",
"vite": "^2.9.5" "vite": "^2.9.5"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@playwright/experimental-ct-vue", "name": "@playwright/experimental-ct-vue",
"version": "1.23.0-next", "version": "1.23.4",
"description": "Playwright Component Testing for Vue", "description": "Playwright Component Testing for Vue",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -23,7 +23,7 @@
}, },
"dependencies": { "dependencies": {
"@vitejs/plugin-vue": "^2.3.1", "@vitejs/plugin-vue": "^2.3.1",
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.4",
"vite": "^2.9.5" "vite": "^2.9.5"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@playwright/experimental-ct-vue2", "name": "@playwright/experimental-ct-vue2",
"version": "1.23.0-next", "version": "1.23.4",
"description": "Playwright Component Testing for Vue2", "description": "Playwright Component Testing for Vue2",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -22,7 +22,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.4",
"vite": "^2.9.5", "vite": "^2.9.5",
"vite-plugin-vue2": "^2.0.1" "vite-plugin-vue2": "^2.0.1"
}, },

View file

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

View file

@ -79,6 +79,7 @@ This project incorporates components from the projects listed below. The origina
- babel-plugin-dynamic-import-node@2.3.3 (https://github.com/airbnb/babel-plugin-dynamic-import-node) - babel-plugin-dynamic-import-node@2.3.3 (https://github.com/airbnb/babel-plugin-dynamic-import-node)
- braces@3.0.2 (https://github.com/micromatch/braces) - braces@3.0.2 (https://github.com/micromatch/braces)
- browserslist@4.20.3 (https://github.com/browserslist/browserslist) - browserslist@4.20.3 (https://github.com/browserslist/browserslist)
- buffer-from@1.1.2 (https://github.com/LinusU/buffer-from)
- call-bind@1.0.2 (https://github.com/ljharb/call-bind) - call-bind@1.0.2 (https://github.com/ljharb/call-bind)
- caniuse-lite@1.0.30001346 (https://github.com/browserslist/caniuse-lite) - caniuse-lite@1.0.30001346 (https://github.com/browserslist/caniuse-lite)
- chalk@2.4.2 (https://github.com/chalk/chalk) - chalk@2.4.2 (https://github.com/chalk/chalk)
@ -133,8 +134,8 @@ This project incorporates components from the projects listed below. The origina
- safe-buffer@5.1.2 (https://github.com/feross/safe-buffer) - safe-buffer@5.1.2 (https://github.com/feross/safe-buffer)
- semver@6.3.0 (https://github.com/npm/node-semver) - semver@6.3.0 (https://github.com/npm/node-semver)
- slash@3.0.0 (https://github.com/sindresorhus/slash) - slash@3.0.0 (https://github.com/sindresorhus/slash)
- source-map-support@0.4.18 (https://github.com/evanw/node-source-map-support) - source-map-support@0.5.21 (https://github.com/evanw/node-source-map-support)
- source-map@0.5.7 (https://github.com/mozilla/source-map) - source-map@0.6.1 (https://github.com/mozilla/source-map)
- stack-utils@2.0.5 (https://github.com/tapjs/stack-utils) - stack-utils@2.0.5 (https://github.com/tapjs/stack-utils)
- supports-color@5.5.0 (https://github.com/chalk/supports-color) - supports-color@5.5.0 (https://github.com/chalk/supports-color)
- supports-color@7.2.0 (https://github.com/chalk/supports-color) - supports-color@7.2.0 (https://github.com/chalk/supports-color)
@ -2262,6 +2263,32 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
========================================= =========================================
END OF browserslist@4.20.3 AND INFORMATION END OF browserslist@4.20.3 AND INFORMATION
%% buffer-from@1.1.2 NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
Copyright (c) 2016, 2018 Linus Unnebäck
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=========================================
END OF buffer-from@1.1.2 AND INFORMATION
%% call-bind@1.0.2 NOTICES AND INFORMATION BEGIN HERE %% call-bind@1.0.2 NOTICES AND INFORMATION BEGIN HERE
========================================= =========================================
MIT License MIT License
@ -3820,7 +3847,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
========================================= =========================================
END OF slash@3.0.0 AND INFORMATION END OF slash@3.0.0 AND INFORMATION
%% source-map-support@0.4.18 NOTICES AND INFORMATION BEGIN HERE %% source-map-support@0.5.21 NOTICES AND INFORMATION BEGIN HERE
========================================= =========================================
The MIT License (MIT) The MIT License (MIT)
@ -3844,9 +3871,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
========================================= =========================================
END OF source-map-support@0.4.18 AND INFORMATION END OF source-map-support@0.5.21 AND INFORMATION
%% source-map@0.5.7 NOTICES AND INFORMATION BEGIN HERE %% source-map@0.6.1 NOTICES AND INFORMATION BEGIN HERE
========================================= =========================================
Copyright (c) 2009-2011, Mozilla Foundation and contributors Copyright (c) 2009-2011, Mozilla Foundation and contributors
All rights reserved. All rights reserved.
@ -3876,7 +3903,7 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
========================================= =========================================
END OF source-map@0.5.7 AND INFORMATION END OF source-map@0.6.1 AND INFORMATION
%% stack-utils@2.0.5 NOTICES AND INFORMATION BEGIN HERE %% stack-utils@2.0.5 NOTICES AND INFORMATION BEGIN HERE
========================================= =========================================
@ -3975,6 +4002,6 @@ END OF to-regex-range@5.0.1 AND INFORMATION
SUMMARY BEGIN HERE SUMMARY BEGIN HERE
========================================= =========================================
Total Packages: 136 Total Packages: 137
========================================= =========================================
END OF SUMMARY END OF SUMMARY

View file

@ -21,14 +21,17 @@ const esbuild = require('esbuild');
// Can be removed once source-map-support was is fixed. // Can be removed once source-map-support was is fixed.
/** @type{import('esbuild').Plugin} */ /** @type{import('esbuild').Plugin} */
let patchSourceMapSupportHideBufferDeprecationWarning = { let patchSource = {
name: 'patch-source-map-support-deprecation', name: 'patch-source-map-support-deprecation',
setup(build) { setup(build) {
build.onResolve({ filter: /^source-map-support$/ }, () => { build.onResolve({ filter: /^source-map-support$/ }, () => {
const originalPath = require.resolve('source-map-support'); const originalPath = require.resolve('source-map-support');
const patchedPath = path.join(path.dirname(originalPath), path.basename(originalPath, '.js') + '.pw-patched.js'); const patchedPath = path.join(path.dirname(originalPath), path.basename(originalPath, '.js') + '.pw-patched.js');
let sourceFileContent = fs.readFileSync(originalPath, 'utf8') let sourceFileContent = fs.readFileSync(originalPath, 'utf8');
sourceFileContent = sourceFileContent.replace(/new Buffer\(rawData/g, 'Buffer.from(rawData'); // source-map-support is overwriting __PW_ZONE__ with func in core if source maps are present.
const original = `return state.nextPosition.name || originalFunctionName();`;
const insertedLine = `if (state.nextPosition.name === 'func') return originalFunctionName() || 'func';`;
sourceFileContent = sourceFileContent.replace(original, insertedLine + original);
fs.writeFileSync(patchedPath, sourceFileContent); fs.writeFileSync(patchedPath, sourceFileContent);
return { path: patchedPath } return { path: patchedPath }
}); });
@ -39,7 +42,7 @@ esbuild.build({
entryPoints: [path.join(__dirname, 'src/utilsBundleImpl.ts')], entryPoints: [path.join(__dirname, 'src/utilsBundleImpl.ts')],
bundle: true, bundle: true,
outdir: path.join(__dirname, '../../lib'), outdir: path.join(__dirname, '../../lib'),
plugins: [patchSourceMapSupportHideBufferDeprecationWarning], plugins: [patchSource],
format: 'cjs', format: 'cjs',
platform: 'node', platform: 'node',
target: 'ES2019', target: 'ES2019',

View file

@ -11,7 +11,7 @@
"json5": "2.2.1", "json5": "2.2.1",
"open": "8.4.0", "open": "8.4.0",
"pirates": "4.0.4", "pirates": "4.0.4",
"source-map-support": "0.4.18" "source-map-support": "0.5.21"
}, },
"devDependencies": { "devDependencies": {
"@types/source-map-support": "^0.5.4" "@types/source-map-support": "^0.5.4"
@ -26,6 +26,11 @@
"source-map": "^0.6.0" "source-map": "^0.6.0"
} }
}, },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"node_modules/define-lazy-prop": { "node_modules/define-lazy-prop": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@ -98,25 +103,17 @@
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/source-map-support": { "node_modules/source-map-support": {
"version": "0.4.18", "version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dependencies": { "dependencies": {
"source-map": "^0.5.6" "buffer-from": "^1.0.0",
} "source-map": "^0.6.0"
},
"node_modules/source-map-support/node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"engines": {
"node": ">=0.10.0"
} }
} }
}, },
@ -130,6 +127,11 @@
"source-map": "^0.6.0" "source-map": "^0.6.0"
} }
}, },
"buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"define-lazy-prop": { "define-lazy-prop": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@ -171,22 +173,15 @@
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
"dev": true
}, },
"source-map-support": { "source-map-support": {
"version": "0.4.18", "version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"requires": { "requires": {
"source-map": "^0.5.6" "buffer-from": "^1.0.0",
}, "source-map": "^0.6.0"
"dependencies": {
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
}
} }
} }
} }

View file

@ -12,7 +12,7 @@
"json5": "2.2.1", "json5": "2.2.1",
"open": "8.4.0", "open": "8.4.0",
"pirates": "4.0.4", "pirates": "4.0.4",
"source-map-support": "0.4.18" "source-map-support": "0.5.21"
}, },
"devDependencies": { "devDependencies": {
"@types/source-map-support": "^0.5.4" "@types/source-map-support": "^0.5.4"

View file

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

View file

@ -18,7 +18,9 @@ import fs from 'fs';
import url from 'url'; import url from 'url';
import { transformHook, resolveHook } from './transform'; import { transformHook, resolveHook } from './transform';
async function resolve(specifier: string, context: { parentURL: string }, defaultResolve: any) { // Node < 18.6: defaultResolve takes 3 arguments.
// Node >= 18.6: nextResolve from the chain takes 2 arguments.
async function resolve(specifier: string, context: { parentURL?: string }, defaultResolve: Function) {
if (context.parentURL && context.parentURL.startsWith('file://')) { if (context.parentURL && context.parentURL.startsWith('file://')) {
const filename = url.fileURLToPath(context.parentURL); const filename = url.fileURLToPath(context.parentURL);
const resolved = resolveHook(filename, specifier); const resolved = resolveHook(filename, specifier);
@ -28,12 +30,15 @@ async function resolve(specifier: string, context: { parentURL: string }, defaul
return defaultResolve(specifier, context, defaultResolve); return defaultResolve(specifier, context, defaultResolve);
} }
async function load(moduleUrl: string, context: any, defaultLoad: any) { // Node < 18.6: defaultLoad takes 3 arguments.
// Node >= 18.6: nextLoad from the chain takes 2 arguments.
async function load(moduleUrl: string, context: any, defaultLoad: Function) {
if (moduleUrl.startsWith('file://') && (moduleUrl.endsWith('.ts') || moduleUrl.endsWith('.tsx'))) { if (moduleUrl.startsWith('file://') && (moduleUrl.endsWith('.ts') || moduleUrl.endsWith('.tsx'))) {
const filename = url.fileURLToPath(moduleUrl); const filename = url.fileURLToPath(moduleUrl);
const code = fs.readFileSync(filename, 'utf-8'); const code = fs.readFileSync(filename, 'utf-8');
const source = transformHook(code, filename, moduleUrl); const source = transformHook(code, filename, moduleUrl);
return { format: 'module', source }; // shortCurcuit is required by Node >= 18.6 to designate no more loaders should be called.
return { format: 'module', source, shortCircuit: true };
} }
return defaultLoad(moduleUrl, context, defaultLoad); return defaultLoad(moduleUrl, context, defaultLoad);
} }

View file

@ -64,8 +64,8 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
await use(require('playwright-core')); await use(require('playwright-core'));
} }
}, { scope: 'worker' } ], }, { scope: 'worker' } ],
headless: [ true, { scope: 'worker', option: true } ], headless: [ ({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: 'worker', option: true } ],
channel: [ undefined, { scope: 'worker', option: true } ], channel: [ ({ launchOptions }, use) => use(launchOptions.channel), { scope: 'worker', option: true } ],
launchOptions: [ {}, { scope: 'worker', option: true } ], launchOptions: [ {}, { scope: 'worker', option: true } ],
connectOptions: [ undefined, { scope: 'worker', option: true } ], connectOptions: [ undefined, { scope: 'worker', option: true } ],
screenshot: [ 'off', { scope: 'worker', option: true } ], screenshot: [ 'off', { scope: 'worker', option: true } ],
@ -135,32 +135,31 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
await browser.close(); await browser.close();
}, { scope: 'worker' } ], }, { scope: 'worker' } ],
acceptDownloads: [ true, { option: true } ], acceptDownloads: [ ({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true } ],
bypassCSP: [ undefined, { option: true } ], bypassCSP: [ ({ contextOptions }, use) => use(contextOptions.bypassCSP), { option: true } ],
colorScheme: [ undefined, { option: true } ], colorScheme: [ ({ contextOptions }, use) => use(contextOptions.colorScheme), { option: true } ],
deviceScaleFactor: [ undefined, { option: true } ], deviceScaleFactor: [ ({ contextOptions }, use) => use(contextOptions.deviceScaleFactor), { option: true } ],
extraHTTPHeaders: [ undefined, { option: true } ], extraHTTPHeaders: [ ({ contextOptions }, use) => use(contextOptions.extraHTTPHeaders), { option: true } ],
geolocation: [ undefined, { option: true } ], geolocation: [ ({ contextOptions }, use) => use(contextOptions.geolocation), { option: true } ],
har: [undefined, { option: true }], hasTouch: [ ({ contextOptions }, use) => use(contextOptions.hasTouch), { option: true } ],
hasTouch: [ undefined, { option: true } ], httpCredentials: [ ({ contextOptions }, use) => use(contextOptions.httpCredentials), { option: true } ],
httpCredentials: [ undefined, { option: true } ], ignoreHTTPSErrors: [ ({ contextOptions }, use) => use(contextOptions.ignoreHTTPSErrors), { option: true } ],
ignoreHTTPSErrors: [ undefined, { option: true } ], isMobile: [ ({ contextOptions }, use) => use(contextOptions.isMobile), { option: true } ],
isMobile: [ undefined, { option: true } ], javaScriptEnabled: [ ({ contextOptions }, use) => use(contextOptions.javaScriptEnabled ?? true), { option: true } ],
javaScriptEnabled: [ true, { option: true } ], locale: [ ({ contextOptions }, use) => use(contextOptions.locale ?? 'en-US'), { option: true } ],
locale: [ 'en-US', { option: true } ], offline: [ ({ contextOptions }, use) => use(contextOptions.offline), { option: true } ],
offline: [ undefined, { option: true } ], permissions: [ ({ contextOptions }, use) => use(contextOptions.permissions), { option: true } ],
permissions: [ undefined, { option: true } ], proxy: [ ({ contextOptions }, use) => use(contextOptions.proxy), { option: true } ],
proxy: [ undefined, { option: true } ], storageState: [ ({ contextOptions }, use) => use(contextOptions.storageState), { option: true } ],
storageState: [ undefined, { option: true } ], timezoneId: [ ({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true } ],
timezoneId: [ undefined, { option: true } ], userAgent: [ ({ contextOptions }, use) => use(contextOptions.userAgent), { option: true } ],
userAgent: [ undefined, { option: true } ], viewport: [({ contextOptions }, use) => use(contextOptions.viewport === undefined ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true }],
viewport: [ { width: 1280, height: 720 }, { option: true } ],
actionTimeout: [ 0, { option: true } ], actionTimeout: [ 0, { option: true } ],
navigationTimeout: [ 0, { option: true } ], navigationTimeout: [ 0, { option: true } ],
baseURL: [ async ({ }, use) => { baseURL: [ async ({ }, use) => {
await use(process.env.PLAYWRIGHT_TEST_BASE_URL); await use(process.env.PLAYWRIGHT_TEST_BASE_URL);
}, { option: true } ], }, { option: true } ],
serviceWorkers: [ 'allow', { option: true } ], serviceWorkers: [ ({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? 'allow'), { option: true } ],
contextOptions: [ {}, { option: true } ], contextOptions: [ {}, { option: true } ],
_combinedContextOptions: async ({ _combinedContextOptions: async ({
@ -169,7 +168,6 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
colorScheme, colorScheme,
deviceScaleFactor, deviceScaleFactor,
extraHTTPHeaders, extraHTTPHeaders,
har,
hasTouch, hasTouch,
geolocation, geolocation,
httpCredentials, httpCredentials,
@ -201,8 +199,6 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
options.extraHTTPHeaders = extraHTTPHeaders; options.extraHTTPHeaders = extraHTTPHeaders;
if (geolocation !== undefined) if (geolocation !== undefined)
options.geolocation = geolocation; options.geolocation = geolocation;
if (har !== undefined)
options.har = har;
if (hasTouch !== undefined) if (hasTouch !== undefined)
options.hasTouch = hasTouch; options.hasTouch = hasTouch;
if (httpCredentials !== undefined) if (httpCredentials !== undefined)

View file

@ -27,7 +27,7 @@ import { assert, calculateSha1 } from 'playwright-core/lib/utils';
import type { AddressInfo } from 'net'; import type { AddressInfo } from 'net';
let previewServer: PreviewServer; let previewServer: PreviewServer;
const VERSION = 4; const VERSION = 5;
type CtConfig = { type CtConfig = {
ctPort?: number; ctPort?: number;
@ -37,6 +37,7 @@ type CtConfig = {
}; };
const importReactRE = /(^|\n)import\s+(\*\s+as\s+)?React(,|\s+)/; const importReactRE = /(^|\n)import\s+(\*\s+as\s+)?React(,|\s+)/;
const compiledReactRE = /(const|var)\s+React\s*=/;
export function createPlugin( export function createPlugin(
registerSourceFile: string, registerSourceFile: string,
@ -275,7 +276,7 @@ function vitePlugin(registerSource: string, relativeTemplateDir: string, buildIn
} }
// Vite React plugin will do this for .jsx files, but not .js files. // Vite React plugin will do this for .jsx files, but not .js files.
if (id.endsWith('.js') && content.includes('React.createElement') && !content.match(importReactRE)) { if (id.endsWith('.js') && content.includes('React.createElement') && !content.match(importReactRE) && !content.match(compiledReactRE)) {
const code = `import React from 'react';\n${content}`; const code = `import React from 'react';\n${content}`;
return { code, map: { mappings: '' } }; return { code, map: { mappings: '' } };
} }

View file

@ -26,7 +26,7 @@ import { tsConfigLoader } from './third_party/tsconfig-loader';
import Module from 'module'; import Module from 'module';
import type { BabelTransformFunction } from './babelBundle'; import type { BabelTransformFunction } from './babelBundle';
const version = 12; const version = 13;
const cacheDir = process.env.PWTEST_CACHE_DIR || path.join(os.tmpdir(), 'playwright-transform-cache'); const cacheDir = process.env.PWTEST_CACHE_DIR || path.join(os.tmpdir(), 'playwright-transform-cache');
const sourceMaps: Map<string, string> = new Map(); const sourceMaps: Map<string, string> = new Map();

View file

@ -2492,7 +2492,6 @@ type BrowserName = 'chromium' | 'firefox' | 'webkit';
type BrowserChannel = Exclude<LaunchOptions['channel'], undefined>; type BrowserChannel = Exclude<LaunchOptions['channel'], undefined>;
type ColorScheme = Exclude<BrowserContextOptions['colorScheme'], undefined>; type ColorScheme = Exclude<BrowserContextOptions['colorScheme'], undefined>;
type ExtraHTTPHeaders = Exclude<BrowserContextOptions['extraHTTPHeaders'], undefined>; type ExtraHTTPHeaders = Exclude<BrowserContextOptions['extraHTTPHeaders'], undefined>;
type HAROptions = Exclude<BrowserContextOptions['har'], undefined>;
type Proxy = Exclude<BrowserContextOptions['proxy'], undefined>; type Proxy = Exclude<BrowserContextOptions['proxy'], undefined>;
type StorageState = Exclude<BrowserContextOptions['storageState'], undefined>; type StorageState = Exclude<BrowserContextOptions['storageState'], undefined>;
type ServiceWorkerPolicy = Exclude<BrowserContextOptions['serviceWorkers'], undefined>; type ServiceWorkerPolicy = Exclude<BrowserContextOptions['serviceWorkers'], undefined>;
@ -2700,15 +2699,6 @@ export interface PlaywrightTestOptions {
*/ */
extraHTTPHeaders: ExtraHTTPHeaders | undefined; extraHTTPHeaders: ExtraHTTPHeaders | undefined;
geolocation: Geolocation | undefined; geolocation: Geolocation | undefined;
/**
* If specified the network requests that are made in the context will be served from the HAR file. Read more about
* [Replaying from HAR](https://playwright.dev/docs/network#replaying-from-har).
*
* > NOTE: Playwright will not serve requests intercepted by Service Worker from the HAR file. See
* [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend disabling Service Workers when using
* request interception by setting `Browser.newContext.serviceWorkers` to `'block'`.
*/
har: HAROptions | undefined;
/** /**
* Specifies if viewport supports touch events. Defaults to false. * Specifies if viewport supports touch events. Defaults to false.
*/ */

View file

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

View file

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

View file

@ -108,7 +108,7 @@ export class SnapshotRenderer {
// First try locating exact resource belonging to this frame. // First try locating exact resource belonging to this frame.
for (const resource of this._resources) { for (const resource of this._resources) {
if (resource._monotonicTime >= snapshot.timestamp) if (typeof resource._monotonicTime === 'number' && resource._monotonicTime >= snapshot.timestamp)
break; break;
if (resource._frameref !== snapshot.frameId) if (resource._frameref !== snapshot.frameId)
continue; continue;
@ -121,7 +121,7 @@ export class SnapshotRenderer {
if (!result) { if (!result) {
// Then fall back to resource with this URL to account for memory cache. // Then fall back to resource with this URL to account for memory cache.
for (const resource of this._resources) { for (const resource of this._resources) {
if (resource._monotonicTime >= snapshot.timestamp) if (typeof resource._monotonicTime === 'number' && resource._monotonicTime >= snapshot.timestamp)
break; break;
if (resource.request.url === url) if (resource.request.url === url)
return resource; return resource;

View file

@ -116,7 +116,7 @@ export function resourcesForAction(action: ActionTraceEvent): ResourceSnapshot[]
const nextAction = next(action); const nextAction = next(action);
result = context(action).resources.filter(resource => { result = context(action).resources.filter(resource => {
return resource._monotonicTime > action.metadata.startTime && (!nextAction || resource._monotonicTime < nextAction.metadata.startTime); return typeof resource._monotonicTime === 'number' && resource._monotonicTime > action.metadata.startTime && (!nextAction || resource._monotonicTime < nextAction.metadata.startTime);
}); });
(action as any)[resourcesSymbol] = result; (action as any)[resourcesSymbol] = result;
return result; return result;

View file

@ -22,7 +22,6 @@
], ],
"entries": [ "entries": [
{ {
"_requestref": "request@ee2a0dc164935fcd4d9432d37b245f3c",
"_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea", "_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea",
"_monotonicTime": 270572145.898, "_monotonicTime": 270572145.898,
"startedDateTime": "2022-06-10T04:27:32.146Z", "startedDateTime": "2022-06-10T04:27:32.146Z",
@ -92,7 +91,6 @@
"_securityDetails": {} "_securityDetails": {}
}, },
{ {
"_requestref": "request@f2ff0fd79321ff90d0bc1b5d6fc13bad",
"_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea", "_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea",
"_monotonicTime": 270572174.683, "_monotonicTime": 270572174.683,
"startedDateTime": "2022-06-10T04:27:32.172Z", "startedDateTime": "2022-06-10T04:27:32.172Z",
@ -162,7 +160,6 @@
"_securityDetails": {} "_securityDetails": {}
}, },
{ {
"_requestref": "request@f2ff0fd79321ff90d0bc1b5d6fc13bac",
"_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea", "_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea",
"_monotonicTime": 270572174.683, "_monotonicTime": 270572174.683,
"startedDateTime": "2022-06-10T04:27:32.174Z", "startedDateTime": "2022-06-10T04:27:32.174Z",
@ -232,7 +229,6 @@
"_securityDetails": {} "_securityDetails": {}
}, },
{ {
"_requestref": "request@9626f59acb1f4a95f25112d32e9f7f60",
"_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea", "_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea",
"_monotonicTime": 270572175.042, "_monotonicTime": 270572175.042,
"startedDateTime": "2022-06-10T04:27:32.175Z", "startedDateTime": "2022-06-10T04:27:32.175Z",
@ -297,7 +293,6 @@
"_securityDetails": {} "_securityDetails": {}
}, },
{ {
"_requestref": "request@d7ee53396148a663b819c348c53b03fb",
"_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea", "_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea",
"_monotonicTime": 270572181.822, "_monotonicTime": 270572181.822,
"startedDateTime": "2022-06-10T04:27:32.182Z", "startedDateTime": "2022-06-10T04:27:32.182Z",

View file

@ -22,7 +22,6 @@
], ],
"entries": [ "entries": [
{ {
"_requestref": "request@7d6e0ddb1e1e25f6e5c4a7c943c0bae1",
"_frameref": "frame@3767e074ecde4cb8372abba2f6f9bb4f", "_frameref": "frame@3767e074ecde4cb8372abba2f6f9bb4f",
"_monotonicTime": 110928357.437, "_monotonicTime": 110928357.437,
"startedDateTime": "2022-06-16T21:41:23.951Z", "startedDateTime": "2022-06-16T21:41:23.951Z",
@ -201,7 +200,6 @@
} }
}, },
{ {
"_requestref": "request@5c7a316ee46a095bda80c23ddc8c740d",
"_frameref": "frame@3767e074ecde4cb8372abba2f6f9bb4f", "_frameref": "frame@3767e074ecde4cb8372abba2f6f9bb4f",
"_monotonicTime": 110928427.603, "_monotonicTime": 110928427.603,
"startedDateTime": "2022-06-16T21:41:24.022Z", "startedDateTime": "2022-06-16T21:41:24.022Z",
@ -358,7 +356,6 @@
"_securityDetails": {} "_securityDetails": {}
}, },
{ {
"_requestref": "request@17664a6093c12c97d41efbff3a502adb",
"_frameref": "frame@3767e074ecde4cb8372abba2f6f9bb4f", "_frameref": "frame@3767e074ecde4cb8372abba2f6f9bb4f",
"_monotonicTime": 110928455.901, "_monotonicTime": 110928455.901,
"startedDateTime": "2022-06-16T21:41:24.050Z", "startedDateTime": "2022-06-16T21:41:24.050Z",

View file

@ -22,7 +22,6 @@
], ],
"entries": [ "entries": [
{ {
"_requestref": "request@ee2a0dc164935fcd4d9432d37b245f3c",
"_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea", "_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea",
"_monotonicTime": 270572145.898, "_monotonicTime": 270572145.898,
"startedDateTime": "2022-06-10T04:27:32.146Z", "startedDateTime": "2022-06-10T04:27:32.146Z",
@ -69,7 +68,7 @@
"size": 12, "size": 12,
"mimeType": "text/html", "mimeType": "text/html",
"compression": 0, "compression": 0,
"_sha1": "har-sha1-main-response.txt" "_file": "har-sha1-main-response.txt"
}, },
"headersSize": 64, "headersSize": 64,
"bodySize": 71, "bodySize": 71,

View file

@ -292,7 +292,7 @@
"cssresize": true, "cssresize": true,
"scrollsnappoints": true, "scrollsnappoints": true,
"shapes": true, "shapes": true,
"textalignlast": false, "textalignlast": true,
"csstransforms": true, "csstransforms": true,
"csstransforms3d": true, "csstransforms3d": true,
"csstransformslevel2": true, "csstransformslevel2": true,

View file

@ -292,7 +292,7 @@
"cssresize": true, "cssresize": true,
"scrollsnappoints": true, "scrollsnappoints": true,
"shapes": true, "shapes": true,
"textalignlast": false, "textalignlast": true,
"csstransforms": true, "csstransforms": true,
"csstransforms3d": true, "csstransforms3d": true,
"csstransformslevel2": true, "csstransformslevel2": true,

View file

@ -196,8 +196,8 @@ test('should serve from HAR', async ({ playwright, asset }) => {
const harPath = asset('har-fulfill.har'); const harPath = asset('har-fulfill.har');
const app = await playwright._electron.launch({ const app = await playwright._electron.launch({
args: [path.join(__dirname, 'electron-window-app.js')], args: [path.join(__dirname, 'electron-window-app.js')],
har: { path: harPath },
}); });
app.context().routeFromHAR(harPath);
const page = await app.firstWindow(); const page = await app.firstWindow();
// await page.goto('https://playwright.dev/'); // await page.goto('https://playwright.dev/');
await page.goto('http://no.playwright/'); await page.goto('http://no.playwright/');

View file

@ -19,11 +19,11 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import extractZip from '../../packages/playwright-core/bundles/zip/node_modules/extract-zip'; import extractZip from '../../packages/playwright-core/bundles/zip/node_modules/extract-zip';
it('should fulfill from har, matching the method and following redirects', async ({ contextFactory, isAndroid, asset }) => { it('should context.routeFromHAR, matching the method and following redirects', async ({ context, isAndroid, asset }) => {
it.fixme(isAndroid); it.fixme(isAndroid);
const path = asset('har-fulfill.har'); const path = asset('har-fulfill.har');
const context = await contextFactory({ har: { path } }); await context.routeFromHAR(path);
const page = await context.newPage(); const page = await context.newPage();
await page.goto('http://no.playwright/'); await page.goto('http://no.playwright/');
// HAR contains a redirect for the script that should be followed automatically. // HAR contains a redirect for the script that should be followed automatically.
@ -32,42 +32,55 @@ it('should fulfill from har, matching the method and following redirects', async
await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(255, 0, 0)'); await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(255, 0, 0)');
}); });
it('fallback:continue should continue when not found in har', async ({ contextFactory, server, isAndroid, asset }) => { it('should page.routeFromHAR, matching the method and following redirects', async ({ context, isAndroid, asset }) => {
it.fixme(isAndroid); it.fixme(isAndroid);
const path = asset('har-fulfill.har'); const path = asset('har-fulfill.har');
const context = await contextFactory({ har: { path, fallback: 'continue' } }); const page = await context.newPage();
await page.routeFromHAR(path);
await page.goto('http://no.playwright/');
// HAR contains a redirect for the script that should be followed automatically.
expect(await page.evaluate('window.value')).toBe('foo');
// HAR contains a POST for the css file that should not be used.
await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(255, 0, 0)');
});
it('fallback:continue should continue when not found in har', async ({ context, server, isAndroid, asset }) => {
it.fixme(isAndroid);
const path = asset('har-fulfill.har');
await context.routeFromHAR(path, { notFound: 'fallback' });
const page = await context.newPage(); const page = await context.newPage();
await page.goto(server.PREFIX + '/one-style.html'); await page.goto(server.PREFIX + '/one-style.html');
await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)'); await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)');
}); });
it('by default should abort requests not found in har', async ({ contextFactory, server, isAndroid, asset }) => { it('by default should abort requests not found in har', async ({ context, server, isAndroid, asset }) => {
it.fixme(isAndroid); it.fixme(isAndroid);
const path = asset('har-fulfill.har'); const path = asset('har-fulfill.har');
const context = await contextFactory({ har: { path } }); await context.routeFromHAR(path);
const page = await context.newPage(); const page = await context.newPage();
const error = await page.goto(server.EMPTY_PAGE).catch(e => e); const error = await page.goto(server.EMPTY_PAGE).catch(e => e);
expect(error instanceof Error).toBe(true); expect(error instanceof Error).toBe(true);
}); });
it('fallback:continue should continue requests on bad har', async ({ contextFactory, server, isAndroid }, testInfo) => { it('fallback:continue should continue requests on bad har', async ({ context, server, isAndroid }, testInfo) => {
it.fixme(isAndroid); it.fixme(isAndroid);
const path = testInfo.outputPath('test.har'); const path = testInfo.outputPath('test.har');
fs.writeFileSync(path, JSON.stringify({ log: {} }), 'utf-8'); fs.writeFileSync(path, JSON.stringify({ log: {} }), 'utf-8');
const context = await contextFactory({ har: { path, fallback: 'continue' } }); await context.routeFromHAR(path, { notFound: 'fallback' });
const page = await context.newPage(); const page = await context.newPage();
await page.goto(server.PREFIX + '/one-style.html'); await page.goto(server.PREFIX + '/one-style.html');
await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)'); await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)');
}); });
it('should only handle requests matching url filter', async ({ contextFactory, isAndroid, asset }) => { it('should only handle requests matching url filter', async ({ context, isAndroid, asset }) => {
it.fixme(isAndroid); it.fixme(isAndroid);
const path = asset('har-fulfill.har'); const path = asset('har-fulfill.har');
const context = await contextFactory({ har: { path, urlFilter: '**/*.js' } }); await context.routeFromHAR(path, { notFound: 'fallback', url: '**/*.js' });
const page = await context.newPage(); const page = await context.newPage();
await context.route('http://no.playwright/', async route => { await context.route('http://no.playwright/', async route => {
expect(route.request().url()).toBe('http://no.playwright/'); expect(route.request().url()).toBe('http://no.playwright/');
@ -83,11 +96,51 @@ it('should only handle requests matching url filter', async ({ contextFactory, i
await expect(page.locator('body')).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); await expect(page.locator('body')).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
}); });
it('should support regex filter', async ({ contextFactory, isAndroid, asset }) => { it('should only context.routeFromHAR requests matching url filter', async ({ context, isAndroid, asset }) => {
it.fixme(isAndroid); it.fixme(isAndroid);
const path = asset('har-fulfill.har'); const path = asset('har-fulfill.har');
const context = await contextFactory({ har: { path, urlFilter: /.*(\.js|.*\.css|no.playwright\/)$/ } }); await context.routeFromHAR(path, { url: '**/*.js' });
const page = await context.newPage();
await context.route('http://no.playwright/', async route => {
expect(route.request().url()).toBe('http://no.playwright/');
await route.fulfill({
status: 200,
contentType: 'text/html',
body: '<script src="./script.js"></script><div>hello</div>',
});
});
await page.goto('http://no.playwright/');
// HAR contains a redirect for the script that should be followed automatically.
expect(await page.evaluate('window.value')).toBe('foo');
await expect(page.locator('body')).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
});
it('should only page.routeFromHAR requests matching url filter', async ({ context, isAndroid, asset }) => {
it.fixme(isAndroid);
const path = asset('har-fulfill.har');
const page = await context.newPage();
await page.routeFromHAR(path, { url: '**/*.js' });
await context.route('http://no.playwright/', async route => {
expect(route.request().url()).toBe('http://no.playwright/');
await route.fulfill({
status: 200,
contentType: 'text/html',
body: '<script src="./script.js"></script><div>hello</div>',
});
});
await page.goto('http://no.playwright/');
// HAR contains a redirect for the script that should be followed automatically.
expect(await page.evaluate('window.value')).toBe('foo');
await expect(page.locator('body')).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
});
it('should support regex filter', async ({ context, isAndroid, asset }) => {
it.fixme(isAndroid);
const path = asset('har-fulfill.har');
await context.routeFromHAR(path, { url: /.*(\.js|.*\.css|no.playwright\/)$/ });
const page = await context.newPage(); const page = await context.newPage();
await page.goto('http://no.playwright/'); await page.goto('http://no.playwright/');
expect(await page.evaluate('window.value')).toBe('foo'); expect(await page.evaluate('window.value')).toBe('foo');
@ -98,7 +151,8 @@ it('newPage should fulfill from har, matching the method and following redirects
it.fixme(isAndroid); it.fixme(isAndroid);
const path = asset('har-fulfill.har'); const path = asset('har-fulfill.har');
const page = await browser.newPage({ har: { path } }); const page = await browser.newPage();
await page.routeFromHAR(path);
await page.goto('http://no.playwright/'); await page.goto('http://no.playwright/');
// HAR contains a redirect for the script that should be followed automatically. // HAR contains a redirect for the script that should be followed automatically.
expect(await page.evaluate('window.value')).toBe('foo'); expect(await page.evaluate('window.value')).toBe('foo');
@ -107,14 +161,15 @@ it('newPage should fulfill from har, matching the method and following redirects
await page.close(); await page.close();
}); });
it('should change document URL after redirected navigation', async ({ contextFactory, isAndroid, asset }) => { it('should change document URL after redirected navigation', async ({ context, isAndroid, asset }) => {
it.fixme(isAndroid); it.fixme(isAndroid);
const path = asset('har-redirect.har'); const path = asset('har-redirect.har');
const context = await contextFactory({ har: { path } }); await context.routeFromHAR(path);
const page = await context.newPage(); const page = await context.newPage();
const [response] = await Promise.all([ const [response] = await Promise.all([
page.waitForNavigation(), page.waitForNavigation(),
page.waitForURL('https://www.theverge.com/'),
page.goto('https://theverge.com/') page.goto('https://theverge.com/')
]); ]);
await expect(page).toHaveURL('https://www.theverge.com/'); await expect(page).toHaveURL('https://www.theverge.com/');
@ -122,11 +177,28 @@ it('should change document URL after redirected navigation', async ({ contextFac
expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/'); expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/');
}); });
it('should goBack to redirected navigation', async ({ contextFactory, isAndroid, asset, server }) => { it('should change document URL after redirected navigation on click', async ({ server, context, isAndroid, asset }) => {
it.fixme(isAndroid); it.fixme(isAndroid);
const path = asset('har-redirect.har'); const path = asset('har-redirect.har');
const context = await contextFactory({ har: { path, urlFilter: /.*theverge.*/ } }); await context.routeFromHAR(path, { url: /.*theverge.*/ });
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.setContent(`<a href="https://theverge.com/">click me</a>`);
const [response] = await Promise.all([
page.waitForNavigation(),
page.click('text=click me'),
]);
await expect(page).toHaveURL('https://www.theverge.com/');
expect(response.request().url()).toBe('https://www.theverge.com/');
expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/');
});
it('should goBack to redirected navigation', async ({ context, isAndroid, asset, server }) => {
it.fixme(isAndroid);
const path = asset('har-redirect.har');
await context.routeFromHAR(path, { url: /.*theverge.*/ });
const page = await context.newPage(); const page = await context.newPage();
await page.goto('https://theverge.com/'); await page.goto('https://theverge.com/');
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
@ -137,11 +209,11 @@ it('should goBack to redirected navigation', async ({ contextFactory, isAndroid,
expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/'); expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/');
}); });
it('should goForward to redirected navigation', async ({ contextFactory, isAndroid, asset, server }) => { it('should goForward to redirected navigation', async ({ context, isAndroid, asset, server }) => {
it.fixme(isAndroid); it.fixme(isAndroid);
const path = asset('har-redirect.har'); const path = asset('har-redirect.har');
const context = await contextFactory({ har: { path, urlFilter: /.*theverge.*/ } }); await context.routeFromHAR(path, { url: /.*theverge.*/ });
const page = await context.newPage(); const page = await context.newPage();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await expect(page).toHaveURL(server.EMPTY_PAGE); await expect(page).toHaveURL(server.EMPTY_PAGE);
@ -155,11 +227,11 @@ it('should goForward to redirected navigation', async ({ contextFactory, isAndro
expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/'); expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/');
}); });
it('should reload redirected navigation', async ({ contextFactory, isAndroid, asset, server }) => { it('should reload redirected navigation', async ({ context, isAndroid, asset, server }) => {
it.fixme(isAndroid); it.fixme(isAndroid);
const path = asset('har-redirect.har'); const path = asset('har-redirect.har');
const context = await contextFactory({ har: { path, urlFilter: /.*theverge.*/ } }); await context.routeFromHAR(path, { url: /.*theverge.*/ });
const page = await context.newPage(); const page = await context.newPage();
await page.goto('https://theverge.com/'); await page.goto('https://theverge.com/');
await expect(page).toHaveURL('https://www.theverge.com/'); await expect(page).toHaveURL('https://www.theverge.com/');
@ -169,11 +241,11 @@ it('should reload redirected navigation', async ({ contextFactory, isAndroid, as
expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/'); expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/');
}); });
it('should fulfill from har with content in a file', async ({ contextFactory, isAndroid, asset }) => { it('should fulfill from har with content in a file', async ({ context, isAndroid, asset }) => {
it.fixme(isAndroid); it.fixme(isAndroid);
const path = asset('har-sha1.har'); const path = asset('har-sha1.har');
const context = await contextFactory({ har: { path } }); await context.routeFromHAR(path);
const page = await context.newPage(); const page = await context.newPage();
await page.goto('http://no.playwright/'); await page.goto('http://no.playwright/');
expect(await page.content()).toBe('<html><head></head><body>Hello, world</body></html>'); expect(await page.content()).toBe('<html><head></head><body>Hello, world</body></html>');
@ -183,12 +255,34 @@ it('should round-trip har.zip', async ({ contextFactory, isAndroid, server }, te
it.fixme(isAndroid); it.fixme(isAndroid);
const harPath = testInfo.outputPath('har.zip'); const harPath = testInfo.outputPath('har.zip');
const context1 = await contextFactory({ recordHar: { path: harPath } }); const context1 = await contextFactory({ recordHar: { mode: 'minimal', path: harPath } });
const page1 = await context1.newPage(); const page1 = await context1.newPage();
await page1.goto(server.PREFIX + '/one-style.html'); await page1.goto(server.PREFIX + '/one-style.html');
await context1.close(); await context1.close();
const context2 = await contextFactory({ har: { path: harPath, fallback: 'abort' } }); const context2 = await contextFactory();
await context2.routeFromHAR(harPath, { notFound: 'abort' });
const page2 = await context2.newPage();
await page2.goto(server.PREFIX + '/one-style.html');
expect(await page2.content()).toContain('hello, world!');
await expect(page2.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)');
});
it('should produce extracted zip', async ({ contextFactory, isAndroid, server }, testInfo) => {
it.fixme(isAndroid);
const harPath = testInfo.outputPath('har.har');
const context1 = await contextFactory({ recordHar: { mode: 'minimal', path: harPath, content: 'attach' } });
const page1 = await context1.newPage();
await page1.goto(server.PREFIX + '/one-style.html');
await context1.close();
expect(fs.existsSync(harPath)).toBeTruthy();
const har = fs.readFileSync(harPath, 'utf-8');
expect(har).not.toContain('background-color');
const context2 = await contextFactory();
await context2.routeFromHAR(harPath, { notFound: 'abort' });
const page2 = await context2.newPage(); const page2 = await context2.newPage();
await page2.goto(server.PREFIX + '/one-style.html'); await page2.goto(server.PREFIX + '/one-style.html');
expect(await page2.content()).toContain('hello, world!'); expect(await page2.content()).toContain('hello, world!');
@ -199,7 +293,7 @@ it('should round-trip extracted har.zip', async ({ contextFactory, isAndroid, se
it.fixme(isAndroid); it.fixme(isAndroid);
const harPath = testInfo.outputPath('har.zip'); const harPath = testInfo.outputPath('har.zip');
const context1 = await contextFactory({ recordHar: { path: harPath } }); const context1 = await contextFactory({ recordHar: { mode: 'minimal', path: harPath } });
const page1 = await context1.newPage(); const page1 = await context1.newPage();
await page1.goto(server.PREFIX + '/one-style.html'); await page1.goto(server.PREFIX + '/one-style.html');
await context1.close(); await context1.close();
@ -207,7 +301,8 @@ it('should round-trip extracted har.zip', async ({ contextFactory, isAndroid, se
const harDir = testInfo.outputPath('hardir'); const harDir = testInfo.outputPath('hardir');
await extractZip(harPath, { dir: harDir }); await extractZip(harPath, { dir: harDir });
const context2 = await contextFactory({ har: { path: path.join(harDir, 'har.har') } }); const context2 = await contextFactory();
await context2.routeFromHAR(path.join(harDir, 'har.har'));
const page2 = await context2.newPage(); const page2 = await context2.newPage();
await page2.goto(server.PREFIX + '/one-style.html'); await page2.goto(server.PREFIX + '/one-style.html');
expect(await page2.content()).toContain('hello, world!'); expect(await page2.content()).toContain('hello, world!');
@ -222,7 +317,7 @@ it('should round-trip har with postData', async ({ contextFactory, isAndroid, se
}); });
const harPath = testInfo.outputPath('har.zip'); const harPath = testInfo.outputPath('har.zip');
const context1 = await contextFactory({ recordHar: { path: harPath } }); const context1 = await contextFactory({ recordHar: { mode: 'minimal', path: harPath } });
const page1 = await context1.newPage(); const page1 = await context1.newPage();
await page1.goto(server.EMPTY_PAGE); await page1.goto(server.EMPTY_PAGE);
const fetchFunction = async (body: string) => { const fetchFunction = async (body: string) => {
@ -235,7 +330,8 @@ it('should round-trip har with postData', async ({ contextFactory, isAndroid, se
expect(await page1.evaluate(fetchFunction, '3')).toBe('3'); expect(await page1.evaluate(fetchFunction, '3')).toBe('3');
await context1.close(); await context1.close();
const context2 = await contextFactory({ har: { path: harPath } }); const context2 = await contextFactory();
await context2.routeFromHAR(harPath);
const page2 = await context2.newPage(); const page2 = await context2.newPage();
await page2.goto(server.EMPTY_PAGE); await page2.goto(server.EMPTY_PAGE);
expect(await page2.evaluate(fetchFunction, '1')).toBe('1'); expect(await page2.evaluate(fetchFunction, '1')).toBe('1');
@ -252,7 +348,7 @@ it('should disambiguate by header', async ({ contextFactory, isAndroid, server }
}); });
const harPath = testInfo.outputPath('har.zip'); const harPath = testInfo.outputPath('har.zip');
const context1 = await contextFactory({ recordHar: { path: harPath } }); const context1 = await contextFactory({ recordHar: { mode: 'minimal', path: harPath } });
const page1 = await context1.newPage(); const page1 = await context1.newPage();
await page1.goto(server.EMPTY_PAGE); await page1.goto(server.EMPTY_PAGE);
@ -274,7 +370,8 @@ it('should disambiguate by header', async ({ contextFactory, isAndroid, server }
expect(await page1.evaluate(fetchFunction, 'baz3')).toBe('baz3'); expect(await page1.evaluate(fetchFunction, 'baz3')).toBe('baz3');
await context1.close(); await context1.close();
const context2 = await contextFactory({ har: { path: harPath } }); const context2 = await contextFactory();
await context2.routeFromHAR(harPath);
const page2 = await context2.newPage(); const page2 = await context2.newPage();
await page2.goto(server.EMPTY_PAGE); await page2.goto(server.EMPTY_PAGE);
expect(await page2.evaluate(fetchFunction, 'baz1')).toBe('baz1'); expect(await page2.evaluate(fetchFunction, 'baz1')).toBe('baz1');
@ -282,3 +379,57 @@ it('should disambiguate by header', async ({ contextFactory, isAndroid, server }
expect(await page2.evaluate(fetchFunction, 'baz3')).toBe('baz3'); expect(await page2.evaluate(fetchFunction, 'baz3')).toBe('baz3');
expect(await page2.evaluate(fetchFunction, 'baz4')).toBe('baz1'); expect(await page2.evaluate(fetchFunction, 'baz4')).toBe('baz1');
}); });
it('should update har.zip for context', async ({ contextFactory, isAndroid, server }, testInfo) => {
it.fixme(isAndroid);
const harPath = testInfo.outputPath('har.zip');
const context1 = await contextFactory();
await context1.routeFromHAR(harPath, { update: true });
const page1 = await context1.newPage();
await page1.goto(server.PREFIX + '/one-style.html');
await context1.close();
const context2 = await contextFactory();
await context2.routeFromHAR(harPath, { notFound: 'abort' });
const page2 = await context2.newPage();
await page2.goto(server.PREFIX + '/one-style.html');
expect(await page2.content()).toContain('hello, world!');
await expect(page2.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)');
});
it('should update har.zip for page', async ({ contextFactory, isAndroid, server }, testInfo) => {
it.fixme(isAndroid);
const harPath = testInfo.outputPath('har.zip');
const context1 = await contextFactory();
const page1 = await context1.newPage();
await page1.routeFromHAR(harPath, { update: true });
await page1.goto(server.PREFIX + '/one-style.html');
await context1.close();
const context2 = await contextFactory();
const page2 = await context2.newPage();
await page2.routeFromHAR(harPath, { notFound: 'abort' });
await page2.goto(server.PREFIX + '/one-style.html');
expect(await page2.content()).toContain('hello, world!');
await expect(page2.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)');
});
it('should update extracted har.zip for page', async ({ contextFactory, isAndroid, server }, testInfo) => {
it.fixme(isAndroid);
const harPath = testInfo.outputPath('har.har');
const context1 = await contextFactory();
const page1 = await context1.newPage();
await page1.routeFromHAR(harPath, { update: true });
await page1.goto(server.PREFIX + '/one-style.html');
await context1.close();
const context2 = await contextFactory();
const page2 = await context2.newPage();
await page2.routeFromHAR(harPath, { notFound: 'abort' });
await page2.goto(server.PREFIX + '/one-style.html');
expect(await page2.content()).toContain('hello, world!');
await expect(page2.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)');
});

View file

@ -268,6 +268,26 @@ it('should chain fallback', async ({ context, page, server }) => {
expect(intercepted).toEqual([3, 2, 1]); expect(intercepted).toEqual([3, 2, 1]);
}); });
it('should chain fallback w/ dynamic URL', async ({ context, page, server }) => {
const intercepted = [];
await context.route('**/bar', route => {
intercepted.push(1);
route.fallback({ url: server.EMPTY_PAGE });
});
await context.route('**/foo', route => {
intercepted.push(2);
route.fallback({ url: 'http://localhost/bar' });
});
await context.route('**/empty.html', route => {
intercepted.push(3);
route.fallback({ url: 'http://localhost/foo' });
});
await page.goto(server.EMPTY_PAGE);
expect(intercepted).toEqual([3, 2, 1]);
});
it('should not chain fulfill', async ({ context, page, server }) => { it('should not chain fulfill', async ({ context, page, server }) => {
let failed = false; let failed = false;
await context.route('**/empty.html', route => { await context.route('**/empty.html', route => {

View file

@ -228,7 +228,8 @@ it('should support har option', async ({ isAndroid, launchPersistent, asset }) =
it.fixme(isAndroid); it.fixme(isAndroid);
const path = asset('har-fulfill.har'); const path = asset('har-fulfill.har');
const { page } = await launchPersistent({ har: { path } }); const { page } = await launchPersistent();
await page.routeFromHAR(path);
await page.goto('http://no.playwright/'); await page.goto('http://no.playwright/');
// HAR contains a redirect for the script that should be followed automatically. // HAR contains a redirect for the script that should be followed automatically.
expect(await page.evaluate('window.value')).toBe('foo'); expect(await page.evaluate('window.value')).toBe('foo');

View file

@ -291,7 +291,7 @@ it('should omit content', async ({ contextFactory, server }, testInfo) => {
await page.evaluate(() => fetch('/pptr.png').then(r => r.arrayBuffer())); await page.evaluate(() => fetch('/pptr.png').then(r => r.arrayBuffer()));
const log = await getLog(); const log = await getLog();
expect(log.entries[0].response.content.text).toBe(undefined); expect(log.entries[0].response.content.text).toBe(undefined);
expect(log.entries[0].response.content._sha1).toBe(undefined); expect(log.entries[0].response.content._file).toBe(undefined);
}); });
it('should omit content legacy', async ({ contextFactory, server }, testInfo) => { it('should omit content legacy', async ({ contextFactory, server }, testInfo) => {
@ -300,7 +300,7 @@ it('should omit content legacy', async ({ contextFactory, server }, testInfo) =>
await page.evaluate(() => fetch('/pptr.png').then(r => r.arrayBuffer())); await page.evaluate(() => fetch('/pptr.png').then(r => r.arrayBuffer()));
const log = await getLog(); const log = await getLog();
expect(log.entries[0].response.content.text).toBe(undefined); expect(log.entries[0].response.content.text).toBe(undefined);
expect(log.entries[0].response.content._sha1).toBe(undefined); expect(log.entries[0].response.content._file).toBe(undefined);
}); });
it('should attach content', async ({ contextFactory, server }, testInfo) => { it('should attach content', async ({ contextFactory, server }, testInfo) => {
@ -312,19 +312,19 @@ it('should attach content', async ({ contextFactory, server }, testInfo) => {
expect(log.entries[0].response.content.encoding).toBe(undefined); expect(log.entries[0].response.content.encoding).toBe(undefined);
expect(log.entries[0].response.content.mimeType).toBe('text/html; charset=utf-8'); expect(log.entries[0].response.content.mimeType).toBe('text/html; charset=utf-8');
expect(log.entries[0].response.content._sha1).toContain('75841480e2606c03389077304342fac2c58ccb1b'); expect(log.entries[0].response.content._file).toContain('75841480e2606c03389077304342fac2c58ccb1b');
expect(log.entries[0].response.content.size).toBeGreaterThanOrEqual(96); expect(log.entries[0].response.content.size).toBeGreaterThanOrEqual(96);
expect(log.entries[0].response.content.compression).toBe(0); expect(log.entries[0].response.content.compression).toBe(0);
expect(log.entries[1].response.content.encoding).toBe(undefined); expect(log.entries[1].response.content.encoding).toBe(undefined);
expect(log.entries[1].response.content.mimeType).toBe('text/css; charset=utf-8'); expect(log.entries[1].response.content.mimeType).toBe('text/css; charset=utf-8');
expect(log.entries[1].response.content._sha1).toContain('79f739d7bc88e80f55b9891a22bf13a2b4e18adb'); expect(log.entries[1].response.content._file).toContain('79f739d7bc88e80f55b9891a22bf13a2b4e18adb');
expect(log.entries[1].response.content.size).toBeGreaterThanOrEqual(37); expect(log.entries[1].response.content.size).toBeGreaterThanOrEqual(37);
expect(log.entries[1].response.content.compression).toBe(0); expect(log.entries[1].response.content.compression).toBe(0);
expect(log.entries[2].response.content.encoding).toBe(undefined); expect(log.entries[2].response.content.encoding).toBe(undefined);
expect(log.entries[2].response.content.mimeType).toBe('image/png'); expect(log.entries[2].response.content.mimeType).toBe('image/png');
expect(log.entries[2].response.content._sha1).toContain('a4c3a18f0bb83f5d9fe7ce561e065c36205762fa'); expect(log.entries[2].response.content._file).toContain('a4c3a18f0bb83f5d9fe7ce561e065c36205762fa');
expect(log.entries[2].response.content.size).toBeGreaterThanOrEqual(6000); expect(log.entries[2].response.content.size).toBeGreaterThanOrEqual(6000);
expect(log.entries[2].response.content.compression).toBe(0); expect(log.entries[2].response.content.compression).toBe(0);
@ -689,45 +689,6 @@ it('should have different hars for concurrent contexts', async ({ contextFactory
} }
}); });
it('should include _requestref', async ({ contextFactory, server }, testInfo) => {
const { page, getLog } = await pageWithHar(contextFactory, testInfo);
const resp = await page.goto(server.EMPTY_PAGE);
const log = await getLog();
expect(log.entries.length).toBe(1);
const entry = log.entries[0];
expect(entry._requestref).toMatch(/^request@[a-f0-9]{32}$/);
expect(entry._requestref).toBe((resp.request() as any)._guid);
});
it('should include _requestref for redirects', async ({ contextFactory, server }, testInfo) => {
server.setRedirect('/start', '/one-more');
server.setRedirect('/one-more', server.EMPTY_PAGE);
const { page, getLog, context } = await pageWithHar(contextFactory, testInfo);
const requests = new Map<string, string>();
context.on('request', request => {
requests.set(request.url(), (request as any)._guid);
});
await page.goto(server.PREFIX + '/start');
const log = await getLog();
expect(log.entries.length).toBe(3);
const entryStart = log.entries[0];
expect(entryStart.request.url).toBe(server.PREFIX + '/start');
expect(entryStart._requestref).toBe(requests.get(entryStart.request.url));
const entryOneMore = log.entries[1];
expect(entryOneMore.request.url).toBe(server.PREFIX + '/one-more');
expect(entryOneMore._requestref).toBe(requests.get(entryOneMore.request.url));
const entryEmptyPage = log.entries[2];
expect(entryEmptyPage.request.url).toBe(server.EMPTY_PAGE);
expect(entryEmptyPage._requestref).toBe(requests.get(entryEmptyPage.request.url));
});
it('should include API request', async ({ contextFactory, server }, testInfo) => { it('should include API request', async ({ contextFactory, server }, testInfo) => {
const { page, getLog } = await pageWithHar(contextFactory, testInfo); const { page, getLog } = await pageWithHar(contextFactory, testInfo);
const url = server.PREFIX + '/simple.json'; const url = server.PREFIX + '/simple.json';

View file

@ -16,6 +16,7 @@
import { browserTest as it, expect } from '../config/browserTest'; import { browserTest as it, expect } from '../config/browserTest';
import fs from 'fs'; import fs from 'fs';
import os from 'os';
async function checkFeatures(name: string, context: any, server: any) { async function checkFeatures(name: string, context: any, server: any) {
try { try {
@ -31,6 +32,7 @@ async function checkFeatures(name: string, context: any, server: any) {
it('safari-14-1', async ({ browser, browserName, platform, server, headless }) => { it('safari-14-1', async ({ browser, browserName, platform, server, headless }) => {
it.skip(browserName !== 'webkit'); it.skip(browserName !== 'webkit');
it.skip(browserName === 'webkit' && parseInt(os.release(), 10) < 20, 'WebKit for macOS 10.15 is frozen.');
const context = await browser.newContext({ const context = await browser.newContext({
deviceScaleFactor: 2 deviceScaleFactor: 2
}); });
@ -73,6 +75,7 @@ it('safari-14-1', async ({ browser, browserName, platform, server, headless }) =
it('mobile-safari-14-1', async ({ playwright, browser, browserName, platform, server, headless }) => { it('mobile-safari-14-1', async ({ playwright, browser, browserName, platform, server, headless }) => {
it.skip(browserName !== 'webkit'); it.skip(browserName !== 'webkit');
it.skip(browserName === 'webkit' && parseInt(os.release(), 10) < 20, 'WebKit for macOS 10.15 is frozen.');
const iPhone = playwright.devices['iPhone 12']; const iPhone = playwright.devices['iPhone 12'];
const context = await browser.newContext(iPhone); const context = await browser.newContext(iPhone);
const { actual, expected } = await checkFeatures('mobile-safari-14-1', context, server); const { actual, expected } = await checkFeatures('mobile-safari-14-1', context, server);

View file

@ -126,8 +126,8 @@ it('should inherit viewport size from browser context', async function({ browser
expect(size).toEqual({ width: 400, height: 500 }); expect(size).toEqual({ width: 400, height: 500 });
}); });
it('should use viewport size from window features', async function({ browser, server, channel }) { it('should use viewport size from window features', async function({ browser, server, browserName }) {
it.fixme(channel === 'chromium-tip-of-tree', 'https://github.com/microsoft/playwright/issues/14787'); it.fixme(browserName === 'chromium', 'https://github.com/microsoft/playwright/issues/14787');
const context = await browser.newContext({ const context = await browser.newContext({
viewport: { width: 700, height: 700 } viewport: { width: 700, height: 700 }
}); });

View file

@ -128,3 +128,7 @@ it('should handle window', async ({ page, browserName, isElectron }) => {
]); ]);
expect(error.message).toBe(browserName === 'chromium' ? 'Window' : '[object Window]'); expect(error.message).toBe(browserName === 'chromium' ? 'Window' : '[object Window]');
}); });
it('should remove a listener of a non-existing event handler', async ({ page }) => {
page.removeListener('pageerror', () => {});
});

View file

@ -184,7 +184,7 @@ it('should override request url', async ({ page, server }) => {
const request = server.waitForRequest('/global-var.html'); const request = server.waitForRequest('/global-var.html');
let url: string; let url: string;
await page.route('**/foo', route => { await page.route('**/global-var.html', route => {
url = route.request().url(); url = route.request().url();
route.continue(); route.continue();
}); });

View file

@ -17,7 +17,7 @@
import { test as base, expect } from './pageTest'; import { test as base, expect } from './pageTest';
import fs from 'fs'; import fs from 'fs';
import type { HARFile, HARResponse } from 'playwright-core/types/har'; import type * as har from 'playwright-core/lib/server/har/har';
const it = base.extend<{ const it = base.extend<{
// We access test servers at 10.0.2.2 from inside the browser on Android, // We access test servers at 10.0.2.2 from inside the browser on Android,
@ -327,7 +327,7 @@ it('should fulfill with har response', async ({ page, isAndroid, asset }) => {
it.fixme(isAndroid); it.fixme(isAndroid);
const harPath = asset('har-fulfill.har'); const harPath = asset('har-fulfill.har');
const har = JSON.parse(await fs.promises.readFile(harPath, 'utf-8')) as HARFile; const har = JSON.parse(await fs.promises.readFile(harPath, 'utf-8')) as har.HARFile;
await page.route('**/*', async route => { await page.route('**/*', async route => {
const response = findResponse(har, route.request().url()); const response = findResponse(har, route.request().url());
const headers = {}; const headers = {};
@ -346,7 +346,7 @@ it('should fulfill with har response', async ({ page, isAndroid, asset }) => {
await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(0, 255, 255)'); await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(0, 255, 255)');
}); });
function findResponse(har: HARFile, url: string): HARResponse { function findResponse(har: har.HARFile, url: string): har.Response {
let entry; let entry;
const originalUrl = url; const originalUrl = url;
while (url.trim()) { while (url.trim()) {

View file

@ -321,6 +321,26 @@ it('should not work with redirects', async ({ page, server }) => {
expect(chain[i].redirectedTo()).toBe(i ? chain[i - 1] : null); expect(chain[i].redirectedTo()).toBe(i ? chain[i - 1] : null);
}); });
it('should chain fallback w/ dynamic URL', async ({ page, server }) => {
const intercepted = [];
await page.route('**/bar', route => {
intercepted.push(1);
route.fallback({ url: server.EMPTY_PAGE });
});
await page.route('**/foo', route => {
intercepted.push(2);
route.fallback({ url: 'http://localhost/bar' });
});
await page.route('**/empty.html', route => {
intercepted.push(3);
route.fallback({ url: 'http://localhost/foo' });
});
await page.goto(server.EMPTY_PAGE);
expect(intercepted).toEqual([3, 2, 1]);
});
it('should work with redirects for subresources', async ({ page, server }) => { it('should work with redirects for subresources', async ({ page, server }) => {
const intercepted = []; const intercepted = [];
await page.route('**/*', route => { await page.route('**/*', route => {

View file

@ -0,0 +1,186 @@
/**
* 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 { test, expect } from './playwright-test-fixtures';
test('should fall back to launchOptions', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
use: {
launchOptions: {
headless: false,
channel: 'chrome',
}
}
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async ({ headless, channel }) => {
expect.soft(headless).toBe(false);
expect.soft(channel).toBe('chrome');
});
`,
}, { workers: 1 });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});
test('should override launchOptions', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
use: {
headless: false,
channel: 'chrome',
launchOptions: {
headless: true,
channel: 'msedge',
}
}
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async ({ headless, channel }) => {
expect.soft(headless).toBe(false);
expect.soft(channel).toBe('chrome');
});
`,
}, { workers: 1 });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});
test('should respect contextOptions', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
use: {
contextOptions: {
acceptDownloads: false,
bypassCSP: true,
colorScheme: 'dark',
deviceScaleFactor: 2,
extraHTTPHeaders: {'foo': 'bar'},
hasTouch: true,
ignoreHTTPSErrors: true,
isMobile: true,
javaScriptEnabled: true,
locale: 'fr-FR',
offline: true,
permissions: ['geolocation'],
timezoneId: 'TIMEZONE',
userAgent: 'UA',
viewport: null
}
}
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async ({ acceptDownloads, bypassCSP, colorScheme, deviceScaleFactor, extraHTTPHeaders, hasTouch, ignoreHTTPSErrors, isMobile, javaScriptEnabled, locale, offline, permissions, timezoneId, userAgent, viewport }) => {
expect.soft(acceptDownloads).toBe(false);
expect.soft(bypassCSP).toBe(true);
expect.soft(colorScheme).toBe('dark');
expect.soft(deviceScaleFactor).toBe(2);
expect.soft(extraHTTPHeaders).toEqual({'foo': 'bar'});
expect.soft(hasTouch).toBe(true);
expect.soft(ignoreHTTPSErrors).toBe(true);
expect.soft(isMobile).toBe(true);
expect.soft(javaScriptEnabled).toBe(true);
expect.soft(locale).toBe('fr-FR');
expect.soft(offline).toBe(true);
expect.soft(permissions).toEqual(['geolocation']);
expect.soft(timezoneId).toBe('TIMEZONE');
expect.soft(userAgent).toBe('UA');
expect.soft(viewport).toBe(null);
});
`,
}, { workers: 1 });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});
test('should override contextOptions', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
use: {
acceptDownloads: false,
bypassCSP: true,
colorScheme: 'dark',
deviceScaleFactor: 2,
extraHTTPHeaders: {'foo': 'bar'},
hasTouch: true,
ignoreHTTPSErrors: true,
isMobile: true,
javaScriptEnabled: true,
locale: 'fr-FR',
offline: true,
permissions: ['geolocation'],
timezoneId: 'TIMEZONE',
userAgent: 'UA',
viewport: null,
contextOptions: {
acceptDownloads: true,
bypassCSP: false,
colorScheme: 'light',
deviceScaleFactor: 1,
extraHTTPHeaders: {'foo': 'bar2'},
hasTouch: false,
ignoreHTTPSErrors: false,
isMobile: false,
javaScriptEnabled: false,
locale: 'en-US',
offline: false,
permissions: [],
timezoneId: 'TIMEZONE 2',
userAgent: 'UA 2',
viewport: { width: 500, height: 500 }
}
}
};
`,
'a.test.ts': `
const { test } = pwt;
test('pass', async ({ acceptDownloads, bypassCSP, colorScheme, deviceScaleFactor, extraHTTPHeaders, hasTouch, ignoreHTTPSErrors, isMobile, javaScriptEnabled, locale, offline, permissions, timezoneId, userAgent, viewport }) => {
expect.soft(acceptDownloads).toBe(false);
expect.soft(bypassCSP).toBe(true);
expect.soft(colorScheme).toBe('dark');
expect.soft(deviceScaleFactor).toBe(2);
expect.soft(extraHTTPHeaders).toEqual({'foo': 'bar'});
expect.soft(hasTouch).toBe(true);
expect.soft(ignoreHTTPSErrors).toBe(true);
expect.soft(isMobile).toBe(true);
expect.soft(javaScriptEnabled).toBe(true);
expect.soft(locale).toBe('fr-FR');
expect.soft(offline).toBe(true);
expect.soft(permissions).toEqual(['geolocation']);
expect.soft(timezoneId).toBe('TIMEZONE');
expect.soft(userAgent).toBe('UA');
expect.soft(viewport).toBe(null);
});
`,
}, { workers: 1 });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});

View file

@ -602,18 +602,3 @@ test('should pass fixture defaults to tests', async ({ runInlineTest }) => {
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1); expect(result.passed).toBe(1);
}); });
test('should support har option', async ({ runInlineTest, asset }) => {
const result = await runInlineTest({
'a.test.ts': `
const { test } = pwt;
test.use({ har: { path: ${JSON.stringify(asset('har-fulfill.har'))} }});
test('pass', async ({ page }) => {
await page.goto('http://no.playwright/');
expect(await page.evaluate('window.value')).toBe('foo');
});
`,
}, { workers: 1 });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});

View file

@ -15,7 +15,7 @@ echo "Building playwright-core package"
node ../../utils/pack_package.js playwright-core ./output/playwright-core.tgz node ../../utils/pack_package.js playwright-core ./output/playwright-core.tgz
echo "Building api.json and protocol.yml" echo "Building api.json and protocol.yml"
node ../../utils/doclint/generateApiJson.js > ./output/api.json API_JSON_MODE=1 node ../../utils/doclint/generateApiJson.js > ./output/api.json
cp ../../packages/playwright-core/src/protocol/protocol.yml ./output/ cp ../../packages/playwright-core/src/protocol/protocol.yml ./output/
function build { function build {

View file

@ -70,19 +70,31 @@ async function run() {
writeAssumeNoop(path.join(PROJECT_DIR, 'README.md'), content, dirtyFiles); writeAssumeNoop(path.join(PROJECT_DIR, 'README.md'), content, dirtyFiles);
} }
// Patch docker version in docs
{
let playwrightVersion = require(path.join(PROJECT_DIR, 'package.json')).version; let playwrightVersion = require(path.join(PROJECT_DIR, 'package.json')).version;
if (playwrightVersion.endsWith('-next')) if (playwrightVersion.endsWith('-next'))
playwrightVersion = playwrightVersion.substring(0, playwrightVersion.indexOf('-next')); playwrightVersion = playwrightVersion.substring(0, playwrightVersion.indexOf('-next'));
// Patch docker version in docs
{
const regex = new RegExp("(mcr.microsoft.com/playwright[^: ]*):?([^ ]*)"); const regex = new RegExp("(mcr.microsoft.com/playwright[^: ]*):?([^ ]*)");
for (const filePath of getAllMarkdownFiles(path.join(PROJECT_DIR, 'docs'))) { for (const filePath of getAllMarkdownFiles(path.join(PROJECT_DIR, 'docs'))) {
let content = fs.readFileSync(filePath).toString(); let content = fs.readFileSync(filePath).toString();
content = content.replace(new RegExp('(mcr.microsoft.com/playwright[^:]*):([\\w\\d-.]+)', 'ig'), (match, imageName, imageVersion) => { content = content.replace(new RegExp('(mcr.microsoft.com/playwright[^:]*):([\\w\\d-.]+)', 'ig'), (match, imageName, imageVersion) => {
return `${imageName}:v${playwrightVersion}-focal`; const [version, distroName] = imageVersion.split('-');
return `${imageName}:v${playwrightVersion}-${distroName ?? 'focal'}`;
}); });
writeAssumeNoop(filePath, content, dirtyFiles); writeAssumeNoop(filePath, content, dirtyFiles);
} }
// Patch pom.xml
{
const introPath = path.join(PROJECT_DIR, 'docs', 'src', 'intro-java.md');
const pomVersionRe = new RegExp('^(\\s*<artifactId>playwright<\\/artifactId>\\n\\s*<version>)(.*)(<\\/version>)$', 'gm');
let content = fs.readFileSync(introPath).toString();
const majorVersion = playwrightVersion.replace(new RegExp('((\\d+\\.){2})(\\d+)'), '$10')
content = content.replace(pomVersionRe, '$1' + majorVersion + '$3');
writeAssumeNoop(introPath, content, dirtyFiles);
}
} }
// Update device descriptors // Update device descriptors

View file

@ -173,7 +173,6 @@ type BrowserName = 'chromium' | 'firefox' | 'webkit';
type BrowserChannel = Exclude<LaunchOptions['channel'], undefined>; type BrowserChannel = Exclude<LaunchOptions['channel'], undefined>;
type ColorScheme = Exclude<BrowserContextOptions['colorScheme'], undefined>; type ColorScheme = Exclude<BrowserContextOptions['colorScheme'], undefined>;
type ExtraHTTPHeaders = Exclude<BrowserContextOptions['extraHTTPHeaders'], undefined>; type ExtraHTTPHeaders = Exclude<BrowserContextOptions['extraHTTPHeaders'], undefined>;
type HAROptions = Exclude<BrowserContextOptions['har'], undefined>;
type Proxy = Exclude<BrowserContextOptions['proxy'], undefined>; type Proxy = Exclude<BrowserContextOptions['proxy'], undefined>;
type StorageState = Exclude<BrowserContextOptions['storageState'], undefined>; type StorageState = Exclude<BrowserContextOptions['storageState'], undefined>;
type ServiceWorkerPolicy = Exclude<BrowserContextOptions['serviceWorkers'], undefined>; type ServiceWorkerPolicy = Exclude<BrowserContextOptions['serviceWorkers'], undefined>;
@ -216,7 +215,6 @@ export interface PlaywrightTestOptions {
deviceScaleFactor: number | undefined; deviceScaleFactor: number | undefined;
extraHTTPHeaders: ExtraHTTPHeaders | undefined; extraHTTPHeaders: ExtraHTTPHeaders | undefined;
geolocation: Geolocation | undefined; geolocation: Geolocation | undefined;
har: HAROptions | undefined;
hasTouch: boolean | undefined; hasTouch: boolean | undefined;
httpCredentials: HTTPCredentials | undefined; httpCredentials: HTTPCredentials | undefined;
ignoreHTTPSErrors: boolean | undefined; ignoreHTTPSErrors: boolean | undefined;

View file

@ -16,7 +16,6 @@
import { Protocol } from 'playwright-core/types/protocol'; import { Protocol } from 'playwright-core/types/protocol';
import { ChildProcess } from 'child_process'; import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { HARResponse } from 'playwright-core/types/har';
import { Readable } from 'stream'; import { Readable } from 'stream';
import { ReadStream } from 'fs'; import { ReadStream } from 'fs';
import { Serializable, EvaluationArgument, PageFunction, PageFunctionOn, SmartHandle, ElementHandleForTag, BindingSource } from 'playwright-core/types/structs'; import { Serializable, EvaluationArgument, PageFunction, PageFunctionOn, SmartHandle, ElementHandleForTag, BindingSource } from 'playwright-core/types/structs';

View file

@ -264,6 +264,9 @@ function innerRenderMdNode(indent, node, lastNode, result, maxColumns) {
if (node.type === 'code') { if (node.type === 'code') {
newLine(); newLine();
if (process.env.API_JSON_MODE)
result.push(`${indent}\`\`\`${node.codeLang}`);
else
result.push(`${indent}\`\`\`${codeLangToHighlighter(node.codeLang)}`); result.push(`${indent}\`\`\`${codeLangToHighlighter(node.codeLang)}`);
for (const line of node.lines) for (const line of node.lines)
result.push(indent + line); result.push(indent + line);