Compare commits

...

12 commits

Author SHA1 Message Date
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
73 changed files with 1061 additions and 814 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,30 @@ 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.url
- `url` <[string]|[RegExp]|[function]\([URL]\):[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.
## 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,30 @@ 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.url
- `url` <[string]|[RegExp]|[function]\([URL]\):[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.
## 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]>
@ -594,6 +563,7 @@ Logger sink for Playwright logging.
`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 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.
- `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`, `attach` mode 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

@ -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.routeFromHAR("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.routeFromHAR("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,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.zip',
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.0-focal`.
### ⚠️ 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

@ -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-%%

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.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "playwright-internal", "name": "playwright-internal",
"version": "1.23.0-next", "version": "1.23.0",
"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.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.23.0-next" "playwright-core": "1.23.0"
}, },
"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.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.23.0-next" "playwright-core": "1.23.0"
}, },
"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.0",
"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.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.0",
"@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.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.0",
"@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.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.0",
"@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.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@playwright/test": "1.23.0-next", "@playwright/test": "1.23.0",
"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.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.23.0-next" "playwright-core": "1.23.0"
}, },
"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.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"playwright-core": "1.23.0-next" "playwright-core": "1.23.0"
}, },
"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.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.23.0-next" "playwright-core": "1.23.0"
}, },
"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.0",
"@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.0",
"@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.0",
"@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.0",
"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.0"
} }
}, },
"@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.0"
} }
}, },
"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.0"
} }
}, },
"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.0"
} }
}, },
"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.0"
} }
}, },
"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.0",
"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.0",
"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.0"
} }
} }

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.0",
"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>();
@ -144,8 +145,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 +270,11 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
await this._channel.setNetworkInterceptionEnabled({ enabled: true }); await this._channel.setNetworkInterceptionEnabled({ enabled: true });
} }
async routeFromHAR(har: string, options: { url?: URLMatch, notFound?: 'abort' | 'fallback' } = {}): Promise<void> {
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)
@ -387,6 +395,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

@ -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

@ -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,11 @@ 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?: URLMatch, notFound?: 'abort' | 'fallback' } = {}): Promise<void> {
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,
@ -3158,17 +3159,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?
@ -2491,10 +2496,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),
@ -1181,9 +1182,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

@ -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

@ -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);
@ -130,11 +130,11 @@ 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 +176,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 +213,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 +249,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

@ -33,6 +33,7 @@ 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, 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`));
@ -41,6 +42,8 @@ export class HarRecorder {
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, 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,8 +60,10 @@ export class HarRecorder {
} }
onContentBlob(sha1: string, buffer: Buffer) { onContentBlob(sha1: string, buffer: Buffer) {
if (this._zipFile) if (!this._zipFile || this._writtenZipEntries.has(sha1))
this._zipFile!.addBuffer(buffer, sha1); return;
this._writtenZipEntries.add(sha1);
this._zipFile!.addBuffer(buffer, sha1);
} }
async flush() { async flush() {
@ -70,7 +75,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 +97,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 {
@ -61,6 +69,14 @@ export class HarTracer {
this._context = context; this._context = context;
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;
} }
@ -92,32 +108,34 @@ export class HarTracer {
return (request as any)[this._entrySymbol]; return (request as any)[this._entrySymbol];
} }
private _ensurePageEntry(page: Page) { private _ensurePageEntry(page: Page): har.Page | undefined {
if (this._options.omitPages)
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 +143,13 @@ export class HarTracer {
}; };
}), true, undefined, 'utility').then(result => { }), true, undefined, 'utility').then(result => {
pageEntry.title = result.title; pageEntry.title = result.title;
pageEntry.pageTimings.onContentLoad = result.domContentLoaded; if (!this._options.omitTiming)
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,7 +157,8 @@ export class HarTracer {
}; };
}), true, undefined, 'utility').then(result => { }), true, undefined, 'utility').then(result => {
pageEntry.title = result.title; pageEntry.title = result.title;
pageEntry.pageTimings.onLoad = result.loaded; if (!this._options.omitTiming)
pageEntry.pageTimings.onLoad = result.loaded;
}).catch(() => {}); }).catch(() => {});
this._addBarrier(page, promise); this._addBarrier(page, promise);
} }
@ -161,11 +180,13 @@ 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);
harEntry.request.cookies = event.cookies; if (!this._options.omitCookies)
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);
harEntry.request.bodySize = event.postData?.length || 0; if (!this._options.omitSizes)
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)
this._delegate.onEntryStarted(harEntry); this._delegate.onEntryStarted(harEntry);
@ -186,7 +207,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)
@ -212,10 +233,12 @@ export class HarTracer {
return; return;
const pageEntry = this._ensurePageEntry(page); const pageEntry = this._ensurePageEntry(page);
const harEntry = createHarEntry(request.method(), url, request.guid, request.frame().guid); const harEntry = createHarEntry(request.method(), url, request.frame().guid, this._options);
harEntry.pageref = pageEntry.id; if (pageEntry)
harEntry.pageref = pageEntry.id;
harEntry.request.postData = this._postDataForRequest(request, this._options.content); harEntry.request.postData = this._postDataForRequest(request, this._options.content);
harEntry.request.bodySize = request.bodySize(); if (!this._options.omitSizes)
harEntry.request.bodySize = request.bodySize();
if (request.redirectedFrom()) { if (request.redirectedFrom()) {
const fromEntry = this._entryForRequest(request.redirectedFrom()!); const fromEntry = this._entryForRequest(request.redirectedFrom()!);
if (fromEntry) if (fromEntry)
@ -238,7 +261,7 @@ export class HarTracer {
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,32 +280,36 @@ export class HarTracer {
this._check(); this._check();
} }
}; };
this._addBarrier(page, compressionCalculationBarrier.barrier); if (compressionCalculationBarrier)
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);
this._addBarrier(page, response.sizes().then(sizes => {
harEntry.response.bodySize = sizes.responseBodySize; if (!this._options.omitSizes) {
harEntry.response.headersSize = sizes.responseHeadersSize; this._addBarrier(page, response.sizes().then(sizes => {
// Fallback for WebKit by calculating it manually harEntry.response.bodySize = sizes.responseBodySize;
harEntry.response._transferSize = response.request().responseSize.transferSize || (sizes.responseHeadersSize + sizes.responseBodySize); harEntry.response.headersSize = sizes.responseHeadersSize;
harEntry.request.headersSize = sizes.requestHeadersSize; // Fallback for WebKit by calculating it manually
compressionCalculationBarrier.setEncodedBodySize(sizes.responseBodySize); harEntry.response._transferSize = response.request().responseSize.transferSize || (sizes.responseHeadersSize + sizes.responseBodySize);
})); harEntry.request.headersSize = sizes.requestHeadersSize;
compressionCalculationBarrier?.setEncodedBodySize(sizes.responseBodySize);
}));
}
} }
private async _onRequestFailed(request: network.Request) { private async _onRequestFailed(request: network.Request) {
@ -301,7 +328,10 @@ export class HarTracer {
content.size = 0; content.size = 0;
return; return;
} }
content.size = buffer.length;
if (!this._options.omitSizes)
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,9 +342,13 @@ 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);
} }
} }
@ -340,43 +374,56 @@ export class HarTracer {
headersSize: -1, headersSize: -1,
bodySize: -1, bodySize: -1,
redirectURL: '', redirectURL: '',
_transferSize: -1 _transferSize: this._options.omitSizes ? undefined : -1
}; };
const timing = response.timing();
if (pageEntry.startedDateTime.valueOf() > timing.startTime) if (!this._options.omitTiming) {
pageEntry.startedDateTime = new Date(timing.startTime); const timing = response.timing();
const dns = timing.domainLookupEnd !== -1 ? helper.millisToRoundishMillis(timing.domainLookupEnd - timing.domainLookupStart) : -1; if (pageEntry && pageEntry.startedDateTime.valueOf() > timing.startTime)
const connect = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.connectStart) : -1; pageEntry.startedDateTime = new Date(timing.startTime);
const ssl = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.secureConnectionStart) : -1; const dns = timing.domainLookupEnd !== -1 ? helper.millisToRoundishMillis(timing.domainLookupEnd - timing.domainLookupStart) : -1;
const wait = timing.responseStart !== -1 ? helper.millisToRoundishMillis(timing.responseStart - timing.requestStart) : -1; const connect = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.connectStart) : -1;
const receive = response.request()._responseEndTiming !== -1 ? helper.millisToRoundishMillis(response.request()._responseEndTiming - timing.responseStart) : -1; const ssl = timing.connectEnd !== -1 ? helper.millisToRoundishMillis(timing.connectEnd - timing.secureConnectionStart) : -1;
harEntry.timings = { const wait = timing.responseStart !== -1 ? helper.millisToRoundishMillis(timing.responseStart - timing.requestStart) : -1;
dns, const receive = response.request()._responseEndTiming !== -1 ? helper.millisToRoundishMillis(response.request()._responseEndTiming - timing.responseStart) : -1;
connect,
ssl, harEntry.timings = {
send: 0, dns,
wait, connect,
receive, ssl,
}; send: 0,
harEntry.time = [dns, connect, ssl, wait, receive].reduce((pre, cur) => cur > 0 ? cur + pre : pre, 0); wait,
this._addBarrier(page, response.serverAddr().then(server => { receive,
if (server?.ipAddress) };
harEntry.serverIPAddress = server.ipAddress; harEntry.time = [dns, connect, ssl, wait, receive].reduce((pre, cur) => cur > 0 ? cur + pre : pre, 0);
if (server?.port) }
harEntry._serverPort = server.port;
})); if (!this._options.omitServerIP) {
this._addBarrier(page, response.securityDetails().then(details => { this._addBarrier(page, response.serverAddr().then(server => {
if (details) if (server?.ipAddress)
harEntry._securityDetails = details; harEntry.serverIPAddress = server.ipAddress;
})); if (server?.port)
harEntry._serverPort = server.port;
}));
}
if (!this._options.omitSecurityDetails) {
this._addBarrier(page, response.securityDetails().then(details => {
if (details)
harEntry._securityDetails = details;
}));
}
this._addBarrier(page, request.rawRequestHeaders().then(headers => { this._addBarrier(page, request.rawRequestHeaders().then(headers => {
for (const header of headers.filter(header => header.name.toLowerCase() === 'cookie')) if (!this._options.omitCookies) {
harEntry.request.cookies.push(...header.value.split(';').map(parseCookie)); for (const header of headers.filter(header => header.name.toLowerCase() === 'cookie'))
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 => {
for (const header of headers.filter(header => header.name.toLowerCase() === 'set-cookie')) if (!this._options.omitCookies) {
harEntry.response.cookies.push(parseCookie(header.value)); for (const header of headers.filter(header => header.name.toLowerCase() === 'set-cookie'))
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,18 +451,20 @@ 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 || []) {
pageEntry.pageTimings.onContentLoad -= pageEntry.startedDateTime.valueOf(); if (typeof pageEntry.pageTimings.onContentLoad === 'number' && pageEntry.pageTimings.onContentLoad >= 0)
else pageEntry.pageTimings.onContentLoad -= pageEntry.startedDateTime.valueOf();
pageEntry.pageTimings.onContentLoad = -1; else
if (pageEntry.pageTimings.onLoad >= 0) pageEntry.pageTimings.onContentLoad = -1;
pageEntry.pageTimings.onLoad -= pageEntry.startedDateTime.valueOf(); if (typeof pageEntry.pageTimings.onLoad === 'number' && pageEntry.pageTimings.onLoad >= 0)
else pageEntry.pageTimings.onLoad -= pageEntry.startedDateTime.valueOf();
pageEntry.pageTimings.onLoad = -1; else
pageEntry.pageTimings.onLoad = -1;
}
} }
this._pageEntries.clear(); this._pageEntries.clear();
return log; return log;
@ -446,8 +495,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 +514,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 +528,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 +543,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

@ -90,6 +90,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
this._precreatedTracesDir = tracesDir; this._precreatedTracesDir = tracesDir;
this._harTracer = new HarTracer(context, this, { this._harTracer = new HarTracer(context, 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, 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,32 @@ 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";
/**
* 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|((url: URL) => boolean);
}): Promise<void>;
/** /**
* Returns the buffer with the captured screenshot. * Returns the buffer with the captured screenshot.
* @param options * @param options
@ -7093,6 +7118,32 @@ 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";
/**
* 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|((url: URL) => boolean);
}): Promise<void>;
/** /**
* > NOTE: Service workers are only supported on Chromium-based browsers. * > NOTE: Service workers are only supported on Chromium-based browsers.
* *
@ -10507,34 +10558,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.
*/ */
@ -10653,6 +10676,12 @@ export interface BrowserType<Unused = {}> {
*/ */
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 +11791,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.
*/ */
@ -11868,6 +11869,12 @@ export interface AndroidDevice {
*/ */
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 +13337,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.
*/ */
@ -13466,6 +13445,12 @@ export interface Browser extends EventEmitter {
*/ */
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 +14187,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).
*/ */
@ -14280,6 +14237,12 @@ export interface Electron {
*/ */
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 +14858,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 +15096,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 +15960,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.
*/ */
@ -16127,6 +16064,12 @@ export interface BrowserContextOptions {
*/ */
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.0",
"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.0",
"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.0",
"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.0",
"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.0",
"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.0",
"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.0",
"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.0",
"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.0",
"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.0"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@playwright/test", "name": "@playwright/test",
"version": "1.23.0-next", "version": "1.23.0",
"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.0"
} }
} }

View file

@ -141,7 +141,6 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
deviceScaleFactor: [ undefined, { option: true } ], deviceScaleFactor: [ undefined, { option: true } ],
extraHTTPHeaders: [ undefined, { option: true } ], extraHTTPHeaders: [ undefined, { option: true } ],
geolocation: [ undefined, { option: true } ], geolocation: [ undefined, { option: true } ],
har: [undefined, { option: true }],
hasTouch: [ undefined, { option: true } ], hasTouch: [ undefined, { option: true } ],
httpCredentials: [ undefined, { option: true } ], httpCredentials: [ undefined, { option: true } ],
ignoreHTTPSErrors: [ undefined, { option: true } ], ignoreHTTPSErrors: [ undefined, { option: true } ],
@ -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

@ -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.0",
"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.0"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "playwright", "name": "playwright",
"version": "1.23.0-next", "version": "1.23.0",
"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.0"
} }
} }

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,13 @@ 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(); 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 +272,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 +280,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 +296,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 +309,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 +327,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 +349,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');

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

@ -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

@ -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

@ -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,7 +264,10 @@ function innerRenderMdNode(indent, node, lastNode, result, maxColumns) {
if (node.type === 'code') { if (node.type === 'code') {
newLine(); newLine();
result.push(`${indent}\`\`\`${codeLangToHighlighter(node.codeLang)}`); if (process.env.API_JSON_MODE)
result.push(`${indent}\`\`\`${node.codeLang}`);
else
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);
result.push(`${indent}\`\`\``); result.push(`${indent}\`\`\``);