Merge branch 'main' into ui-mode-watch-file

This commit is contained in:
Simon Knott 2024-07-26 14:20:32 +02:00
commit 975513e3d9
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
51 changed files with 958 additions and 358 deletions

View file

@ -30,6 +30,7 @@ module.exports = {
"avoidEscape": true, "avoidEscape": true,
"allowTemplateLiterals": true "allowTemplateLiterals": true
}], }],
"jsx-quotes": [2, "prefer-single"],
"no-extra-semi": 2, "no-extra-semi": 2,
"@typescript-eslint/semi": [2], "@typescript-eslint/semi": [2],
"comma-style": [2, "last"], "comma-style": [2, "last"],

View file

@ -219,7 +219,6 @@ jobs:
with: with:
command: npm run itest command: npm run itest
bot-name: "package-installations-${{ matrix.os }}" bot-name: "package-installations-${{ matrix.os }}"
# TODO: figure out why itest fails with 'bash' on Windows.
shell: ${{ matrix.os == 'windows-latest' && 'pwsh' || 'bash' }} shell: ${{ matrix.os == 'windows-latest' && 'pwsh' || 'bash' }}
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}

View file

@ -1,6 +1,6 @@
# 🎭 Playwright # 🎭 Playwright
[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-127.0.6533.57-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-128.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop --> [![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[![Chromium version](https://img.shields.io/badge/chromium-128.0.6613.7-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[![Firefox version](https://img.shields.io/badge/firefox-128.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[![WebKit version](https://img.shields.io/badge/webkit-18.0-blue.svg?logo=safari)](https://webkit.org/)<!-- GEN:stop -->
## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)
@ -8,8 +8,8 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr
| | Linux | macOS | Windows | | | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: | | :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->127.0.6533.57<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Chromium <!-- GEN:chromium-version -->128.0.6613.7<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit <!-- GEN:webkit-version -->18.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->128.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox <!-- GEN:firefox-version -->128.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all browsers on all platforms. Check out [system requirements](https://playwright.dev/docs/intro#system-requirements) for details. Headless execution is supported for all browsers on all platforms. Check out [system requirements](https://playwright.dev/docs/intro#system-requirements) for details.

View file

@ -523,7 +523,7 @@ Does not enforce fixed viewport, allows resizing window in the headed mode.
## context-option-clientCertificates ## context-option-clientCertificates
- `clientCertificates` <[Array]<[Object]>> - `clientCertificates` <[Array]<[Object]>>
- `origin` <[string]> Glob pattern to match against the request origin that the certificate is valid for. - `origin` <[string]> Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port.
- `certPath` ?<[string]> Path to the file with the certificate in PEM format. - `certPath` ?<[string]> Path to the file with the certificate in PEM format.
- `keyPath` ?<[string]> Path to the file with the private key in PEM format. - `keyPath` ?<[string]> Path to the file with the private key in PEM format.
- `pfxPath` ?<[string]> Path to the PFX or PKCS12 encoded private key and certificate chain. - `pfxPath` ?<[string]> Path to the PFX or PKCS12 encoded private key and certificate chain.
@ -533,7 +533,7 @@ TLS Client Authentication allows the server to request a client certificate and
**Details** **Details**
An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be provided with a glob pattern to match the origins that the certificate is valid for. An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for.
:::note :::note
Using Client Certificates in combination with Proxy Servers is not supported. Using Client Certificates in combination with Proxy Servers is not supported.

View file

@ -386,7 +386,7 @@ jobs:
### Fail-Fast ### Fail-Fast
* langs: js * langs: js
Even with sharding enabled, large test suites can take very long to execute. Running changed tests first on PRs will give you a faster feedback loop and use less CI resources. Even with sharding enabled, large test suites can take very long to execute. Running changed test files first on PRs will give you a faster feedback loop and use less CI resources.
```yml js title=".github/workflows/playwright.yml" ```yml js title=".github/workflows/playwright.yml"
name: Playwright Tests name: Playwright Tests

View file

@ -93,7 +93,7 @@ Complete set of Playwright Test options is available in the [configuration file]
| `--max-failures <N>` or `-x`| Stop after the first `N` test failures. Passing `-x` stops after the first failure.| | `--max-failures <N>` or `-x`| Stop after the first `N` test failures. Passing `-x` stops after the first failure.|
| `--no-deps` | Ignore the dependencies between projects and behave as if they were not specified. | | `--no-deps` | Ignore the dependencies between projects and behave as if they were not specified. |
| `--output <dir>` | Directory for artifacts produced by tests, defaults to `test-results`. | | `--output <dir>` | Directory for artifacts produced by tests, defaults to `test-results`. |
| `--only-changed [ref]` | Only run tests that have been changed between working tree and "ref". Defaults to running all uncommitted changes with ref=HEAD. Only supports Git. | | `--only-changed [ref]` | Only run test files that have been changed between working tree and "ref". Defaults to running all uncommitted changes with ref=HEAD. Only supports Git. |
| `--pass-with-no-tests` | Allows the test suite to pass when no files are found. | | `--pass-with-no-tests` | Allows the test suite to pass when no files are found. |
| `--project <name>` | Only run tests from the specified [projects](./test-projects.md), supports '*' wildcard. Defaults to running all projects defined in the configuration file.| | `--project <name>` | Only run tests from the specified [projects](./test-projects.md), supports '*' wildcard. Defaults to running all projects defined in the configuration file.|
| `--quiet` | Whether to suppress stdout and stderr from the tests. | | `--quiet` | Whether to suppress stdout and stderr from the tests. |

View file

@ -70,36 +70,36 @@ export const blank = () => {
}; };
export const externalLink = () => { export const externalLink = () => {
return <svg className='octicon' viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M10.604 1h4.146a.25.25 0 01.25.25v4.146a.25.25 0 01-.427.177L13.03 4.03 9.28 7.78a.75.75 0 01-1.06-1.06l3.75-3.75-1.543-1.543A.25.25 0 0110.604 1zM3.75 2A1.75 1.75 0 002 3.75v8.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 12.25v-3.5a.75.75 0 00-1.5 0v3.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-8.5a.25.25 0 01.25-.25h3.5a.75.75 0 000-1.5h-3.5z"></path></svg>; return <svg className='octicon' viewBox='0 0 16 16' width='16' height='16'><path fill-rule='evenodd' d='M10.604 1h4.146a.25.25 0 01.25.25v4.146a.25.25 0 01-.427.177L13.03 4.03 9.28 7.78a.75.75 0 01-1.06-1.06l3.75-3.75-1.543-1.543A.25.25 0 0110.604 1zM3.75 2A1.75 1.75 0 002 3.75v8.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 12.25v-3.5a.75.75 0 00-1.5 0v3.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-8.5a.25.25 0 01.25-.25h3.5a.75.75 0 000-1.5h-3.5z'></path></svg>;
}; };
export const calendar = () => { export const calendar = () => {
return <svg className='octicon' viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.75 0a.75.75 0 01.75.75V2h5V.75a.75.75 0 011.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0113.25 16H2.75A1.75 1.75 0 011 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 014.75 0zm0 3.5h8.5a.25.25 0 01.25.25V6h-11V3.75a.25.25 0 01.25-.25h2zm-2.25 4v6.75c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25V7.5h-11z"></path></svg>; return <svg className='octicon' viewBox='0 0 16 16' width='16' height='16'><path fill-rule='evenodd' d='M4.75 0a.75.75 0 01.75.75V2h5V.75a.75.75 0 011.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0113.25 16H2.75A1.75 1.75 0 011 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 014.75 0zm0 3.5h8.5a.25.25 0 01.25.25V6h-11V3.75a.25.25 0 01.25-.25h2zm-2.25 4v6.75c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25V7.5h-11z'></path></svg>;
}; };
export const person = () => { export const person = () => {
return <svg className='octicon' viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M10.5 5a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm.061 3.073a4 4 0 10-5.123 0 6.004 6.004 0 00-3.431 5.142.75.75 0 001.498.07 4.5 4.5 0 018.99 0 .75.75 0 101.498-.07 6.005 6.005 0 00-3.432-5.142z"></path></svg>; return <svg className='octicon' viewBox='0 0 16 16' width='16' height='16'><path fill-rule='evenodd' d='M10.5 5a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm.061 3.073a4 4 0 10-5.123 0 6.004 6.004 0 00-3.431 5.142.75.75 0 001.498.07 4.5 4.5 0 018.99 0 .75.75 0 101.498-.07 6.005 6.005 0 00-3.432-5.142z'></path></svg>;
}; };
export const commit = () => { export const commit = () => {
return <svg className='octicon' viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M10.5 7.75a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm1.43.75a4.002 4.002 0 01-7.86 0H.75a.75.75 0 110-1.5h3.32a4.001 4.001 0 017.86 0h3.32a.75.75 0 110 1.5h-3.32z"></path></svg>; return <svg className='octicon' viewBox='0 0 16 16' width='16' height='16'><path fill-rule='evenodd' d='M10.5 7.75a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm1.43.75a4.002 4.002 0 01-7.86 0H.75a.75.75 0 110-1.5h3.32a4.001 4.001 0 017.86 0h3.32a.75.75 0 110 1.5h-3.32z'></path></svg>;
}; };
export const image = () => { export const image = () => {
return <svg className='octicon' viewBox='0 0 48 48' version='1.1' width='20' height='20' aria-hidden='true'> return <svg className='octicon' viewBox='0 0 48 48' version='1.1' width='20' height='20' aria-hidden='true'>
<path xmlns="http://www.w3.org/2000/svg" d="M11.85 32H36.2l-7.35-9.95-6.55 8.7-4.6-6.45ZM7 40q-1.2 0-2.1-.9Q4 38.2 4 37V11q0-1.2.9-2.1Q5.8 8 7 8h34q1.2 0 2.1.9.9.9.9 2.1v26q0 1.2-.9 2.1-.9.9-2.1.9Zm0-29v26-26Zm34 26V11H7v26Z"/> <path xmlns='http://www.w3.org/2000/svg' d='M11.85 32H36.2l-7.35-9.95-6.55 8.7-4.6-6.45ZM7 40q-1.2 0-2.1-.9Q4 38.2 4 37V11q0-1.2.9-2.1Q5.8 8 7 8h34q1.2 0 2.1.9.9.9.9 2.1v26q0 1.2-.9 2.1-.9.9-2.1.9Zm0-29v26-26Zm34 26V11H7v26Z'/>
</svg>; </svg>;
}; };
export const video = () => { export const video = () => {
return <svg className='octicon' viewBox='0 0 48 48' version='1.1' width='20' height='20' aria-hidden='true'> return <svg className='octicon' viewBox='0 0 48 48' version='1.1' width='20' height='20' aria-hidden='true'>
<path xmlns="http://www.w3.org/2000/svg" d="m19.6 32.35 13-8.45-13-8.45ZM7 40q-1.2 0-2.1-.9Q4 38.2 4 37V11q0-1.2.9-2.1Q5.8 8 7 8h34q1.2 0 2.1.9.9.9.9 2.1v26q0 1.2-.9 2.1-.9.9-2.1.9Zm0-3h34V11H7v26Zm0 0V11v26Z"/> <path xmlns='http://www.w3.org/2000/svg' d='m19.6 32.35 13-8.45-13-8.45ZM7 40q-1.2 0-2.1-.9Q4 38.2 4 37V11q0-1.2.9-2.1Q5.8 8 7 8h34q1.2 0 2.1.9.9.9.9 2.1v26q0 1.2-.9 2.1-.9.9-2.1.9Zm0-3h34V11H7v26Zm0 0V11v26Z'/>
</svg>; </svg>;
}; };
export const trace = () => { export const trace = () => {
return <svg className='octicon' viewBox='0 0 48 48' version='1.1' width='20' height='20' aria-hidden='true'> return <svg className='octicon' viewBox='0 0 48 48' version='1.1' width='20' height='20' aria-hidden='true'>
<path xmlns="http://www.w3.org/2000/svg" d="M7 37h9.35V11H7v26Zm12.35 0h9.3V11h-9.3v26Zm12.3 0H41V11h-9.35v26ZM7 40q-1.2 0-2.1-.9Q4 38.2 4 37V11q0-1.2.9-2.1Q5.8 8 7 8h34q1.2 0 2.1.9.9.9.9 2.1v26q0 1.2-.9 2.1-.9.9-2.1.9Z"/> <path xmlns='http://www.w3.org/2000/svg' d='M7 37h9.35V11H7v26Zm12.35 0h9.3V11h-9.3v26Zm12.3 0H41V11h-9.35v26ZM7 40q-1.2 0-2.1-.9Q4 38.2 4 37V11q0-1.2.9-2.1Q5.8 8 7 8h34q1.2 0 2.1.9.9.9.9 2.1v26q0 1.2-.9 2.1-.9.9-2.1.9Z'/>
</svg>; </svg>;
}; };

View file

@ -41,8 +41,8 @@ export const TestFileView: React.FC<React.PropsWithChildren<{
{file.tests.filter(t => filter.matches(t)).map(test => {file.tests.filter(t => filter.matches(t)).map(test =>
<div key={`test-${test.testId}`} className={'test-file-test test-file-test-outcome-' + test.outcome}> <div key={`test-${test.testId}`} className={'test-file-test test-file-test-outcome-' + test.outcome}>
<div className='hbox' style={{ alignItems: 'flex-start' }}> <div className='hbox' style={{ alignItems: 'flex-start' }}>
<div className="hbox"> <div className='hbox'>
<span className="test-file-test-status-icon"> <span className='test-file-test-status-icon'>
{statusIcon(test.outcome)} {statusIcon(test.outcome)}
</span> </span>
<span> <span>

View file

@ -44,11 +44,11 @@ export const TestFilesView: React.FC<{
}, [report, filter]); }, [report, filter]);
return <> return <>
<div className='mt-2 mx-1' style={{ display: 'flex' }}> <div className='mt-2 mx-1' style={{ display: 'flex' }}>
{projectNames.length === 1 && !!projectNames[0] && <div data-testid="project-name" style={{ color: 'var(--color-fg-subtle)' }}>Project: {projectNames[0]}</div>} {projectNames.length === 1 && !!projectNames[0] && <div data-testid='project-name' style={{ color: 'var(--color-fg-subtle)' }}>Project: {projectNames[0]}</div>}
{!filter.empty() && <div data-testid="filtered-tests-count" style={{ color: 'var(--color-fg-subtle)', padding: '0 10px' }}>Filtered: {filteredStats.total} {!!filteredStats.total && ('(' + msToString(filteredStats.duration) + ')')}</div>} {!filter.empty() && <div data-testid='filtered-tests-count' style={{ color: 'var(--color-fg-subtle)', padding: '0 10px' }}>Filtered: {filteredStats.total} {!!filteredStats.total && ('(' + msToString(filteredStats.duration) + ')')}</div>}
<div style={{ flex: 'auto' }}></div> <div style={{ flex: 'auto' }}></div>
<div data-testid="overall-time" style={{ color: 'var(--color-fg-subtle)', marginRight: '10px' }}>{report ? new Date(report.startTime).toLocaleString() : ''}</div> <div data-testid='overall-time' style={{ color: 'var(--color-fg-subtle)', marginRight: '10px' }}>{report ? new Date(report.startTime).toLocaleString() : ''}</div>
<div data-testid="overall-duration" style={{ color: 'var(--color-fg-subtle)' }}>Total time: {msToString(report?.duration ?? 0)}</div> <div data-testid='overall-duration' style={{ color: 'var(--color-fg-subtle)' }}>Total time: {msToString(report?.duration ?? 0)}</div>
</div> </div>
{report && !!report.errors.length && <AutoChip header='Errors' dataTestId='report-errors'> {report && !!report.errors.length && <AutoChip header='Errors' dataTestId='report-errors'>
{report.errors.map((error, index) => <TestErrorView key={'test-report-error-message-' + index} error={error}></TestErrorView>)} {report.errors.map((error, index) => <TestErrorView key={'test-report-error-message-' + index} error={error}></TestErrorView>)}

View file

@ -15,7 +15,7 @@
# See src/server/injected/README.md. # See src/server/injected/README.md.
lib/**/injected/ lib/**/injected/
# Include all binaries that we ship with the package. # Include all binaries that we ship with the package.
!bin/* !bin/**/*
# Include FFMPEG # Include FFMPEG
!third_party/ffmpeg/* !third_party/ffmpeg/*
# Include generated types and entrypoint. # Include generated types and entrypoint.

View file

@ -3,15 +3,15 @@
"browsers": [ "browsers": [
{ {
"name": "chromium", "name": "chromium",
"revision": "1127", "revision": "1128",
"installByDefault": true, "installByDefault": true,
"browserVersion": "127.0.6533.57" "browserVersion": "128.0.6613.7"
}, },
{ {
"name": "chromium-tip-of-tree", "name": "chromium-tip-of-tree",
"revision": "1243", "revision": "1244",
"installByDefault": false, "installByDefault": false,
"browserVersion": "128.0.6613.0" "browserVersion": "129.0.6616.0"
}, },
{ {
"name": "firefox", "name": "firefox",
@ -37,7 +37,7 @@
"mac12": "2009", "mac12": "2009",
"mac12-arm64": "2009" "mac12-arm64": "2009"
}, },
"browserVersion": "17.4" "browserVersion": "18.0"
}, },
{ {
"name": "ffmpeg", "name": "ffmpeg",

View file

@ -151,8 +151,14 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
page._onClose(); page._onClose();
context._onClose(); context._onClose();
} }
browser?._didClose();
connection.close(reason || closeError); connection.close(reason || closeError);
// Give a chance to any API call promises to reject upon page/context closure.
// This happens naturally when we receive page.onClose and browser.onClose from the server
// in separate tasks. However, upon pipe closure we used to dispatch them all synchronously
// here and promises did not have a chance to reject.
// The order of rejects vs closure is a part of the API contract and our test runner
// relies on it to attribute rejections to the right test.
setTimeout(() => browser?._didClose(), 0);
}; };
pipe.on('closed', params => onPipeClosed(params.reason)); pipe.on('closed', params => onPipeClosed(params.reason));
connection.onmessage = message => this._wrapApiCall(() => pipe.send({ message }).catch(() => onPipeClosed()), /* isInternal */ true); connection.onmessage = message => this._wrapApiCall(() => pipe.send({ message }).catch(() => onPipeClosed()), /* isInternal */ true);

View file

@ -112,7 +112,7 @@ export module Protocol {
- from 'checked' to 'selected': states which apply to widgets - from 'checked' to 'selected': states which apply to widgets
- from 'activedescendant' to 'owns' - relationships between elements other than parent/child/sibling. - from 'activedescendant' to 'owns' - relationships between elements other than parent/child/sibling.
*/ */
export type AXPropertyName = "busy"|"disabled"|"editable"|"focusable"|"focused"|"hidden"|"hiddenRoot"|"invalid"|"keyshortcuts"|"settable"|"roledescription"|"live"|"atomic"|"relevant"|"root"|"autocomplete"|"hasPopup"|"level"|"multiselectable"|"orientation"|"multiline"|"readonly"|"required"|"valuemin"|"valuemax"|"valuetext"|"checked"|"expanded"|"modal"|"pressed"|"selected"|"activedescendant"|"controls"|"describedby"|"details"|"errormessage"|"flowto"|"labelledby"|"owns"; export type AXPropertyName = "busy"|"disabled"|"editable"|"focusable"|"focused"|"hidden"|"hiddenRoot"|"invalid"|"keyshortcuts"|"settable"|"roledescription"|"live"|"atomic"|"relevant"|"root"|"autocomplete"|"hasPopup"|"level"|"multiselectable"|"orientation"|"multiline"|"readonly"|"required"|"valuemin"|"valuemax"|"valuetext"|"checked"|"expanded"|"modal"|"pressed"|"selected"|"activedescendant"|"controls"|"describedby"|"details"|"errormessage"|"flowto"|"labelledby"|"owns"|"url";
/** /**
* A node in the accessibility tree. * A node in the accessibility tree.
*/ */
@ -875,7 +875,7 @@ instead of "limited-quirks".
sharedDictionaryError: SharedDictionaryError; sharedDictionaryError: SharedDictionaryError;
request: AffectedRequest; request: AffectedRequest;
} }
export type GenericIssueErrorType = "CrossOriginPortalPostMessageError"|"FormLabelForNameError"|"FormDuplicateIdForInputError"|"FormInputWithNoLabelError"|"FormAutocompleteAttributeEmptyError"|"FormEmptyIdAndNameAttributesForInputError"|"FormAriaLabelledByToNonExistingId"|"FormInputAssignedAutocompleteValueToIdOrNameAttributeError"|"FormLabelHasNeitherForNorNestedInput"|"FormLabelForMatchesNonExistingIdError"|"FormInputHasWrongButWellIntendedAutocompleteValueError"|"ResponseWasBlockedByORB"; export type GenericIssueErrorType = "FormLabelForNameError"|"FormDuplicateIdForInputError"|"FormInputWithNoLabelError"|"FormAutocompleteAttributeEmptyError"|"FormEmptyIdAndNameAttributesForInputError"|"FormAriaLabelledByToNonExistingId"|"FormInputAssignedAutocompleteValueToIdOrNameAttributeError"|"FormLabelHasNeitherForNorNestedInput"|"FormLabelForMatchesNonExistingIdError"|"FormInputHasWrongButWellIntendedAutocompleteValueError"|"ResponseWasBlockedByORB";
/** /**
* Depending on the concrete errorType, different properties are set. * Depending on the concrete errorType, different properties are set.
*/ */
@ -934,7 +934,7 @@ Should be updated alongside RequestIdTokenStatus in
third_party/blink/public/mojom/devtools/inspector_issue.mojom to include third_party/blink/public/mojom/devtools/inspector_issue.mojom to include
all cases except for success. all cases except for success.
*/ */
export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"DisabledInSettings"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenIdpErrorResponse"|"IdTokenCrossSiteIdpErrorResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"|"MissingTransientUserActivation"|"ReplacedByButtonMode"|"RelyingPartyOriginIsOpaque"|"TypeNotMatching"; export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"IdpNotPotentiallyTrustworthy"|"DisabledInSettings"|"DisabledInFlags"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenIdpErrorResponse"|"IdTokenCrossSiteIdpErrorResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"|"MissingTransientUserActivation"|"ReplacedByButtonMode"|"InvalidFieldsSpecified"|"RelyingPartyOriginIsOpaque"|"TypeNotMatching";
export interface FederatedAuthUserInfoRequestIssueDetails { export interface FederatedAuthUserInfoRequestIssueDetails {
federatedAuthUserInfoRequestIssueReason: FederatedAuthUserInfoRequestIssueReason; federatedAuthUserInfoRequestIssueReason: FederatedAuthUserInfoRequestIssueReason;
} }
@ -1480,6 +1480,10 @@ Note that userVisibleOnly = true is the only currently supported type.
* For "clipboard" permission, may specify allowWithoutSanitization. * For "clipboard" permission, may specify allowWithoutSanitization.
*/ */
allowWithoutSanitization?: boolean; allowWithoutSanitization?: boolean;
/**
* For "fullscreen" permission, must specify allowWithoutGesture:true.
*/
allowWithoutGesture?: boolean;
/** /**
* For "camera" permission, may specify panTiltZoom. * For "camera" permission, may specify panTiltZoom.
*/ */
@ -2559,6 +2563,7 @@ stylesheet rules) this rule came from.
* Associated style declaration. * Associated style declaration.
*/ */
style: CSSStyle; style: CSSStyle;
active: boolean;
} }
/** /**
* CSS keyframes rule representation. * CSS keyframes rule representation.
@ -2888,9 +2893,14 @@ attributes) for a DOM node identified by `nodeId`.
*/ */
cssPositionFallbackRules?: CSSPositionFallbackRule[]; cssPositionFallbackRules?: CSSPositionFallbackRule[];
/** /**
* A list of CSS @position-try rules matching this node, based on the position-try-options property. * A list of CSS @position-try rules matching this node, based on the position-try-fallbacks property.
*/ */
cssPositionTryRules?: CSSPositionTryRule[]; cssPositionTryRules?: CSSPositionTryRule[];
/**
* Index of the active fallback in the applied position-try-fallback property,
will not be set if there is no active position-try fallback.
*/
activePositionFallbackIndex?: number;
/** /**
* A list of CSS at-property rules matching this node. * A list of CSS at-property rules matching this node.
*/ */
@ -5907,6 +5917,11 @@ See https://w3c.github.io/sensors/#automation for more information.
xyz?: SensorReadingXYZ; xyz?: SensorReadingXYZ;
quaternion?: SensorReadingQuaternion; quaternion?: SensorReadingQuaternion;
} }
export type PressureSource = "cpu";
export type PressureState = "nominal"|"fair"|"serious"|"critical";
export interface PressureMetadata {
available?: boolean;
}
/** /**
* Enum of image types that can be disabled. * Enum of image types that can be disabled.
*/ */
@ -6190,6 +6205,30 @@ by setSensorOverrideEnabled.
} }
export type setSensorOverrideReadingsReturnValue = { export type setSensorOverrideReadingsReturnValue = {
} }
/**
* Overrides a pressure source of a given type, as used by the Compute
Pressure API, so that updates to PressureObserver.observe() are provided
via setPressureStateOverride instead of being retrieved from
platform-provided telemetry data.
*/
export type setPressureSourceOverrideEnabledParameters = {
enabled: boolean;
source: PressureSource;
metadata?: PressureMetadata;
}
export type setPressureSourceOverrideEnabledReturnValue = {
}
/**
* Provides a given pressure state that will be processed and eventually be
delivered to PressureObserver users. |source| must have been previously
overridden by setPressureSourceOverrideEnabled.
*/
export type setPressureStateOverrideParameters = {
source: PressureSource;
state: PressureState;
}
export type setPressureStateOverrideReturnValue = {
}
/** /**
* Overrides the Idle state. * Overrides the Idle state.
*/ */
@ -6533,6 +6572,54 @@ following the last read). Some types of streams may only support sequential read
} }
} }
export module FileSystem {
export interface File {
name: string;
/**
* Timestamp
*/
lastModified: Network.TimeSinceEpoch;
/**
* Size in bytes
*/
size: number;
type: string;
}
export interface Directory {
name: string;
nestedDirectories: string[];
/**
* Files that are directly nested under this directory.
*/
nestedFiles: File[];
}
export interface BucketFileSystemLocator {
/**
* Storage key
*/
storageKey: Storage.SerializedStorageKey;
/**
* Bucket name. Not passing a `bucketName` will retrieve the default Bucket. (https://developer.mozilla.org/en-US/docs/Web/API/Storage_API#storage_buckets)
*/
bucketName?: string;
/**
* Path to the directory using each path component as an array item.
*/
pathComponents: string[];
}
export type getDirectoryParameters = {
bucketFileSystemLocator: BucketFileSystemLocator;
}
export type getDirectoryReturnValue = {
/**
* Returns the directory object at the path.
*/
directory: Directory;
}
}
export module IndexedDB { export module IndexedDB {
/** /**
* Database with an array of object stores. * Database with an array of object stores.
@ -9048,7 +9135,7 @@ the same request (but not for redirected requests).
initiatorIPAddressSpace: IPAddressSpace; initiatorIPAddressSpace: IPAddressSpace;
privateNetworkRequestPolicy: PrivateNetworkRequestPolicy; privateNetworkRequestPolicy: PrivateNetworkRequestPolicy;
} }
export type CrossOriginOpenerPolicyValue = "SameOrigin"|"SameOriginAllowPopups"|"RestrictProperties"|"UnsafeNone"|"SameOriginPlusCoep"|"RestrictPropertiesPlusCoep"; export type CrossOriginOpenerPolicyValue = "SameOrigin"|"SameOriginAllowPopups"|"RestrictProperties"|"UnsafeNone"|"SameOriginPlusCoep"|"RestrictPropertiesPlusCoep"|"NoopenerAllowPopups";
export interface CrossOriginOpenerPolicyStatus { export interface CrossOriginOpenerPolicyStatus {
value: CrossOriginOpenerPolicyValue; value: CrossOriginOpenerPolicyValue;
reportOnlyValue: CrossOriginOpenerPolicyValue; reportOnlyValue: CrossOriginOpenerPolicyValue;
@ -9731,6 +9818,10 @@ preemptively (e.g. a cache hit).
*/ */
issuedTokenCount?: number; issuedTokenCount?: number;
} }
/**
* Fired once security policy has been updated.
*/
export type policyUpdatedPayload = void;
/** /**
* Fired once when parsing the .wbn file has succeeded. * Fired once when parsing the .wbn file has succeeded.
The event contains the information about the web bundle contents. The event contains the information about the web bundle contents.
@ -11278,7 +11369,7 @@ as an ad.
* All Permissions Policy features. This enum should match the one defined * All Permissions Policy features. This enum should match the one defined
in third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5. in third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5.
*/ */
export type PermissionsPolicyFeature = "accelerometer"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"cross-origin-isolated"|"deferred-fetch"|"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"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking"; export type PermissionsPolicyFeature = "accelerometer"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"cross-origin-isolated"|"deferred-fetch"|"digital-credentials-get"|"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"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking";
/** /**
* Reason for a permissions policy feature to be disabled. * Reason for a permissions policy feature to be disabled.
*/ */
@ -11866,7 +11957,7 @@ https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-expl
/** /**
* 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"|"CookieDisabled"|"HTTPAuthRequired"|"CookieFlushed"|"BroadcastChannelOnMessage"|"WebViewSettingsChanged"|"WebViewJavaScriptObjectChanged"|"WebViewMessageListenerInjected"|"WebViewSafeBrowsingAllowlistChanged"|"WebViewDocumentStartJavascriptChanged"|"WebSocket"|"WebTransport"|"WebRTC"|"MainResourceHasCacheControlNoStore"|"MainResourceHasCacheControlNoCache"|"SubresourceHasCacheControlNoStore"|"SubresourceHasCacheControlNoCache"|"ContainsPlugins"|"DocumentLoaded"|"OutstandingNetworkRequestOthers"|"RequestedMIDIPermission"|"RequestedAudioCapturePermission"|"RequestedVideoCapturePermission"|"RequestedBackForwardCacheBlockedSensors"|"RequestedBackgroundWorkPermission"|"BroadcastChannel"|"WebXR"|"SharedWorker"|"WebLocks"|"WebHID"|"WebShare"|"RequestedStorageAccessGrant"|"WebNfc"|"OutstandingNetworkRequestFetch"|"OutstandingNetworkRequestXHR"|"AppBanner"|"Printing"|"WebDatabase"|"PictureInPicture"|"Portal"|"SpeechRecognizer"|"IdleManager"|"PaymentManager"|"SpeechSynthesis"|"KeyboardLock"|"WebOTPService"|"OutstandingNetworkRequestDirectSocket"|"InjectedJavascript"|"InjectedStyleSheet"|"KeepaliveRequest"|"IndexedDBEvent"|"Dummy"|"JsNetworkRequestReceivedCacheControlNoStoreResource"|"WebRTCSticky"|"WebTransportSticky"|"WebSocketSticky"|"SmartCard"|"LiveMediaStreamTrack"|"UnloadHandler"|"ParserAborted"|"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"|"RequestedByWebViewClient"; 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"|"CookieDisabled"|"HTTPAuthRequired"|"CookieFlushed"|"BroadcastChannelOnMessage"|"WebViewSettingsChanged"|"WebViewJavaScriptObjectChanged"|"WebViewMessageListenerInjected"|"WebViewSafeBrowsingAllowlistChanged"|"WebViewDocumentStartJavascriptChanged"|"WebSocket"|"WebTransport"|"WebRTC"|"MainResourceHasCacheControlNoStore"|"MainResourceHasCacheControlNoCache"|"SubresourceHasCacheControlNoStore"|"SubresourceHasCacheControlNoCache"|"ContainsPlugins"|"DocumentLoaded"|"OutstandingNetworkRequestOthers"|"RequestedMIDIPermission"|"RequestedAudioCapturePermission"|"RequestedVideoCapturePermission"|"RequestedBackForwardCacheBlockedSensors"|"RequestedBackgroundWorkPermission"|"BroadcastChannel"|"WebXR"|"SharedWorker"|"WebLocks"|"WebHID"|"WebShare"|"RequestedStorageAccessGrant"|"WebNfc"|"OutstandingNetworkRequestFetch"|"OutstandingNetworkRequestXHR"|"AppBanner"|"Printing"|"WebDatabase"|"PictureInPicture"|"SpeechRecognizer"|"IdleManager"|"PaymentManager"|"SpeechSynthesis"|"KeyboardLock"|"WebOTPService"|"OutstandingNetworkRequestDirectSocket"|"InjectedJavascript"|"InjectedStyleSheet"|"KeepaliveRequest"|"IndexedDBEvent"|"Dummy"|"JsNetworkRequestReceivedCacheControlNoStoreResource"|"WebRTCSticky"|"WebTransportSticky"|"WebSocketSticky"|"SmartCard"|"LiveMediaStreamTrack"|"UnloadHandler"|"ParserAborted"|"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"|"RequestedByWebViewClient";
/** /**
* Types of not restored reasons for back-forward cache. * Types of not restored reasons for back-forward cache.
*/ */
@ -14053,6 +14144,25 @@ int
eventReportWindows: AttributionReportingEventReportWindows; eventReportWindows: AttributionReportingEventReportWindows;
} }
export type AttributionReportingTriggerDataMatching = "exact"|"modulus"; export type AttributionReportingTriggerDataMatching = "exact"|"modulus";
export interface AttributionReportingAggregatableDebugReportingData {
keyPiece: UnsignedInt128AsBase16;
/**
* number instead of integer because not all uint32 can be represented by
int
*/
value: number;
types: string[];
}
export interface AttributionReportingAggregatableDebugReportingConfig {
/**
* number instead of integer because not all uint32 can be represented by
int, only present for source registrations
*/
budget?: number;
keyPiece: UnsignedInt128AsBase16;
debugData: AttributionReportingAggregatableDebugReportingData[];
aggregationCoordinatorOrigin?: string;
}
export interface AttributionReportingSourceRegistration { export interface AttributionReportingSourceRegistration {
time: Network.TimeSinceEpoch; time: Network.TimeSinceEpoch;
/** /**
@ -14074,8 +14184,10 @@ int
aggregationKeys: AttributionReportingAggregationKeysEntry[]; aggregationKeys: AttributionReportingAggregationKeysEntry[];
debugKey?: UnsignedInt64AsBase10; debugKey?: UnsignedInt64AsBase10;
triggerDataMatching: AttributionReportingTriggerDataMatching; triggerDataMatching: AttributionReportingTriggerDataMatching;
destinationLimitPriority: SignedInt64AsBase10;
aggregatableDebugReportingConfig: AttributionReportingAggregatableDebugReportingConfig;
} }
export type AttributionReportingSourceRegistrationResult = "success"|"internalError"|"insufficientSourceCapacity"|"insufficientUniqueDestinationCapacity"|"excessiveReportingOrigins"|"prohibitedByBrowserPolicy"|"successNoised"|"destinationReportingLimitReached"|"destinationGlobalLimitReached"|"destinationBothLimitsReached"|"reportingOriginsPerSiteLimitReached"|"exceedsMaxChannelCapacity"|"exceedsMaxTriggerStateCardinality"; export type AttributionReportingSourceRegistrationResult = "success"|"internalError"|"insufficientSourceCapacity"|"insufficientUniqueDestinationCapacity"|"excessiveReportingOrigins"|"prohibitedByBrowserPolicy"|"successNoised"|"destinationReportingLimitReached"|"destinationGlobalLimitReached"|"destinationBothLimitsReached"|"reportingOriginsPerSiteLimitReached"|"exceedsMaxChannelCapacity"|"exceedsMaxTriggerStateCardinality"|"destinationPerDayReportingLimitReached";
export type AttributionReportingSourceRegistrationTimeConfig = "include"|"exclude"; export type AttributionReportingSourceRegistrationTimeConfig = "include"|"exclude";
export interface AttributionReportingAggregatableValueDictEntry { export interface AttributionReportingAggregatableValueDictEntry {
key: string; key: string;
@ -14084,6 +14196,7 @@ int
int int
*/ */
value: number; value: number;
filteringId: UnsignedInt64AsBase10;
} }
export interface AttributionReportingAggregatableValueEntry { export interface AttributionReportingAggregatableValueEntry {
values: AttributionReportingAggregatableValueDictEntry[]; values: AttributionReportingAggregatableValueDictEntry[];
@ -14111,10 +14224,12 @@ int
eventTriggerData: AttributionReportingEventTriggerData[]; eventTriggerData: AttributionReportingEventTriggerData[];
aggregatableTriggerData: AttributionReportingAggregatableTriggerData[]; aggregatableTriggerData: AttributionReportingAggregatableTriggerData[];
aggregatableValues: AttributionReportingAggregatableValueEntry[]; aggregatableValues: AttributionReportingAggregatableValueEntry[];
aggregatableFilteringIdMaxBytes: number;
debugReporting: boolean; debugReporting: boolean;
aggregationCoordinatorOrigin?: string; aggregationCoordinatorOrigin?: string;
sourceRegistrationTimeConfig: AttributionReportingSourceRegistrationTimeConfig; sourceRegistrationTimeConfig: AttributionReportingSourceRegistrationTimeConfig;
triggerContextId?: string; triggerContextId?: string;
aggregatableDebugReportingConfig: AttributionReportingAggregatableDebugReportingConfig;
} }
export type AttributionReportingEventLevelResult = "success"|"successDroppedLowerPriority"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"deduplicated"|"excessiveAttributions"|"priorityTooLow"|"neverAttributedSource"|"excessiveReportingOrigins"|"noMatchingSourceFilterData"|"prohibitedByBrowserPolicy"|"noMatchingConfigurations"|"excessiveReports"|"falselyAttributedSource"|"reportWindowPassed"|"notRegistered"|"reportWindowNotStarted"|"noMatchingTriggerData"; export type AttributionReportingEventLevelResult = "success"|"successDroppedLowerPriority"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"deduplicated"|"excessiveAttributions"|"priorityTooLow"|"neverAttributedSource"|"excessiveReportingOrigins"|"noMatchingSourceFilterData"|"prohibitedByBrowserPolicy"|"noMatchingConfigurations"|"excessiveReports"|"falselyAttributedSource"|"reportWindowPassed"|"notRegistered"|"reportWindowNotStarted"|"noMatchingTriggerData";
export type AttributionReportingAggregatableResult = "success"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"excessiveAttributions"|"excessiveReportingOrigins"|"noHistograms"|"insufficientBudget"|"noMatchingSourceFilterData"|"notRegistered"|"prohibitedByBrowserPolicy"|"deduplicated"|"reportWindowPassed"|"excessiveReports"; export type AttributionReportingAggregatableResult = "success"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"excessiveAttributions"|"excessiveReportingOrigins"|"noHistograms"|"insufficientBudget"|"noMatchingSourceFilterData"|"notRegistered"|"prohibitedByBrowserPolicy"|"deduplicated"|"reportWindowPassed"|"excessiveReports";
@ -14980,7 +15095,7 @@ supported.
browserContextId?: Browser.BrowserContextID; browserContextId?: Browser.BrowserContextID;
/** /**
* Provides additional details for specific target types. For example, for * Provides additional details for specific target types. For example, for
the type of "page", this may be set to "portal" or "prerender". the type of "page", this may be set to "prerender".
*/ */
subtype?: string; subtype?: string;
} }
@ -16807,7 +16922,7 @@ possible for multiple rule sets and links to trigger a single attempt.
/** /**
* List of FinalStatus reasons for Prerender2. * List of FinalStatus reasons for Prerender2.
*/ */
export type PrerenderFinalStatus = "Activated"|"Destroyed"|"LowEndDevice"|"InvalidSchemeRedirect"|"InvalidSchemeNavigation"|"NavigationRequestBlockedByCsp"|"MainFrameNavigation"|"MojoBinderPolicy"|"RendererProcessCrashed"|"RendererProcessKilled"|"Download"|"TriggerDestroyed"|"NavigationNotCommitted"|"NavigationBadHttpStatus"|"ClientCertRequested"|"NavigationRequestNetworkError"|"CancelAllHostsForTesting"|"DidFailLoad"|"Stop"|"SslCertificateError"|"LoginAuthRequested"|"UaChangeRequiresReload"|"BlockedByClient"|"AudioOutputDeviceRequested"|"MixedContent"|"TriggerBackgrounded"|"MemoryLimitExceeded"|"DataSaverEnabled"|"TriggerUrlHasEffectiveUrl"|"ActivatedBeforeStarted"|"InactivePageRestriction"|"StartFailed"|"TimeoutBackgrounded"|"CrossSiteRedirectInInitialNavigation"|"CrossSiteNavigationInInitialNavigation"|"SameSiteCrossOriginRedirectNotOptInInInitialNavigation"|"SameSiteCrossOriginNavigationNotOptInInInitialNavigation"|"ActivationNavigationParameterMismatch"|"ActivatedInBackground"|"EmbedderHostDisallowed"|"ActivationNavigationDestroyedBeforeSuccess"|"TabClosedByUserGesture"|"TabClosedWithoutUserGesture"|"PrimaryMainFrameRendererProcessCrashed"|"PrimaryMainFrameRendererProcessKilled"|"ActivationFramePolicyNotCompatible"|"PreloadingDisabled"|"BatterySaverEnabled"|"ActivatedDuringMainFrameNavigation"|"PreloadingUnsupportedByWebContents"|"CrossSiteRedirectInMainFrameNavigation"|"CrossSiteNavigationInMainFrameNavigation"|"SameSiteCrossOriginRedirectNotOptInInMainFrameNavigation"|"SameSiteCrossOriginNavigationNotOptInInMainFrameNavigation"|"MemoryPressureOnTrigger"|"MemoryPressureAfterTriggered"|"PrerenderingDisabledByDevTools"|"SpeculationRuleRemoved"|"ActivatedWithAuxiliaryBrowsingContexts"|"MaxNumOfRunningEagerPrerendersExceeded"|"MaxNumOfRunningNonEagerPrerendersExceeded"|"MaxNumOfRunningEmbedderPrerendersExceeded"|"PrerenderingUrlHasEffectiveUrl"|"RedirectedPrerenderingUrlHasEffectiveUrl"|"ActivationUrlHasEffectiveUrl"|"JavaScriptInterfaceAdded"|"JavaScriptInterfaceRemoved"|"AllPrerenderingCanceled"; export type PrerenderFinalStatus = "Activated"|"Destroyed"|"LowEndDevice"|"InvalidSchemeRedirect"|"InvalidSchemeNavigation"|"NavigationRequestBlockedByCsp"|"MainFrameNavigation"|"MojoBinderPolicy"|"RendererProcessCrashed"|"RendererProcessKilled"|"Download"|"TriggerDestroyed"|"NavigationNotCommitted"|"NavigationBadHttpStatus"|"ClientCertRequested"|"NavigationRequestNetworkError"|"CancelAllHostsForTesting"|"DidFailLoad"|"Stop"|"SslCertificateError"|"LoginAuthRequested"|"UaChangeRequiresReload"|"BlockedByClient"|"AudioOutputDeviceRequested"|"MixedContent"|"TriggerBackgrounded"|"MemoryLimitExceeded"|"DataSaverEnabled"|"TriggerUrlHasEffectiveUrl"|"ActivatedBeforeStarted"|"InactivePageRestriction"|"StartFailed"|"TimeoutBackgrounded"|"CrossSiteRedirectInInitialNavigation"|"CrossSiteNavigationInInitialNavigation"|"SameSiteCrossOriginRedirectNotOptInInInitialNavigation"|"SameSiteCrossOriginNavigationNotOptInInInitialNavigation"|"ActivationNavigationParameterMismatch"|"ActivatedInBackground"|"EmbedderHostDisallowed"|"ActivationNavigationDestroyedBeforeSuccess"|"TabClosedByUserGesture"|"TabClosedWithoutUserGesture"|"PrimaryMainFrameRendererProcessCrashed"|"PrimaryMainFrameRendererProcessKilled"|"ActivationFramePolicyNotCompatible"|"PreloadingDisabled"|"BatterySaverEnabled"|"ActivatedDuringMainFrameNavigation"|"PreloadingUnsupportedByWebContents"|"CrossSiteRedirectInMainFrameNavigation"|"CrossSiteNavigationInMainFrameNavigation"|"SameSiteCrossOriginRedirectNotOptInInMainFrameNavigation"|"SameSiteCrossOriginNavigationNotOptInInMainFrameNavigation"|"MemoryPressureOnTrigger"|"MemoryPressureAfterTriggered"|"PrerenderingDisabledByDevTools"|"SpeculationRuleRemoved"|"ActivatedWithAuxiliaryBrowsingContexts"|"MaxNumOfRunningEagerPrerendersExceeded"|"MaxNumOfRunningNonEagerPrerendersExceeded"|"MaxNumOfRunningEmbedderPrerendersExceeded"|"PrerenderingUrlHasEffectiveUrl"|"RedirectedPrerenderingUrlHasEffectiveUrl"|"ActivationUrlHasEffectiveUrl"|"JavaScriptInterfaceAdded"|"JavaScriptInterfaceRemoved"|"AllPrerenderingCanceled"|"WindowClosed";
/** /**
* Preloading status values, see also PreloadingTriggeringOutcome. This * Preloading status values, see also PreloadingTriggeringOutcome. This
status is shared by prefetchStatusUpdated and prerenderStatusUpdated. status is shared by prefetchStatusUpdated and prerenderStatusUpdated.
@ -17021,6 +17136,10 @@ https://www.iana.org/assignments/media-types/media-types.xhtml
accepts: FileHandlerAccept[]; accepts: FileHandlerAccept[];
displayName: string; displayName: string;
} }
/**
* If user prefers opening the app in browser or an app window.
*/
export type DisplayMode = "standalone"|"browser";
/** /**
@ -17061,7 +17180,7 @@ manifestId.
export type installReturnValue = { export type installReturnValue = {
} }
/** /**
* Uninstals the given manifest_id and closes any opened app windows. * Uninstalls the given manifest_id and closes any opened app windows.
*/ */
export type uninstallParameters = { export type uninstallParameters = {
manifestId: string; manifestId: string;
@ -17090,7 +17209,7 @@ the files. The API returns one or more page Target.TargetIDs which can be
used to attach to via Target.attachToTarget or similar APIs. used to attach to via Target.attachToTarget or similar APIs.
If some files in the parameters cannot be handled by the web app, they will If some files in the parameters cannot be handled by the web app, they will
be ignored. If none of the files can be handled, this API returns an error. be ignored. If none of the files can be handled, this API returns an error.
If no files provided as the parameter, this API also returns an error. If no files are provided as the parameter, this API also returns an error.
According to the definition of the file handlers in the manifest file, one According to the definition of the file handlers in the manifest file, one
Target.TargetID may represent a page handling one or more files. The order Target.TargetID may represent a page handling one or more files. The order
@ -17111,13 +17230,44 @@ TODO(crbug.com/339454034): Check the existences of the input files.
/** /**
* Opens the current page in its web app identified by the manifest id, needs * Opens the current page in its web app identified by the manifest id, needs
to be called on a page target. This function returns immediately without to be called on a page target. This function returns immediately without
waiting for the app finishing loading. waiting for the app to finish loading.
*/ */
export type openCurrentPageInAppParameters = { export type openCurrentPageInAppParameters = {
manifestId: string; manifestId: string;
} }
export type openCurrentPageInAppReturnValue = { export type openCurrentPageInAppReturnValue = {
} }
/**
* Changes user settings of the web app identified by its manifestId. If the
app was not installed, this command returns an error. Unset parameters will
be ignored; unrecognized values will cause an error.
Unlike the ones defined in the manifest files of the web apps, these
settings are provided by the browser and controlled by the users, they
impact the way the browser handling the web apps.
See the comment of each parameter.
*/
export type changeAppUserSettingsParameters = {
manifestId: string;
/**
* If user allows the links clicked on by the user in the app's scope, or
extended scope if the manifest has scope extensions and the flags
`DesktopPWAsLinkCapturingWithScopeExtensions` and
`WebAppEnableScopeExtensions` are enabled.
Note, the API does not support resetting the linkCapturing to the
initial value, uninstalling and installing the web app again will reset
it.
TODO(crbug.com/339453269): Setting this value on ChromeOS is not
supported yet.
*/
linkCapturing?: boolean;
displayMode?: DisplayMode;
}
export type changeAppUserSettingsReturnValue = {
}
} }
/** /**
@ -19824,6 +19974,7 @@ Error was thrown.
"Network.responseReceivedExtraInfo": Network.responseReceivedExtraInfoPayload; "Network.responseReceivedExtraInfo": Network.responseReceivedExtraInfoPayload;
"Network.responseReceivedEarlyHints": Network.responseReceivedEarlyHintsPayload; "Network.responseReceivedEarlyHints": Network.responseReceivedEarlyHintsPayload;
"Network.trustTokenOperationDone": Network.trustTokenOperationDonePayload; "Network.trustTokenOperationDone": Network.trustTokenOperationDonePayload;
"Network.policyUpdated": Network.policyUpdatedPayload;
"Network.subresourceWebBundleMetadataReceived": Network.subresourceWebBundleMetadataReceivedPayload; "Network.subresourceWebBundleMetadataReceived": Network.subresourceWebBundleMetadataReceivedPayload;
"Network.subresourceWebBundleMetadataError": Network.subresourceWebBundleMetadataErrorPayload; "Network.subresourceWebBundleMetadataError": Network.subresourceWebBundleMetadataErrorPayload;
"Network.subresourceWebBundleInnerResponseParsed": Network.subresourceWebBundleInnerResponseParsedPayload; "Network.subresourceWebBundleInnerResponseParsed": Network.subresourceWebBundleInnerResponseParsedPayload;
@ -20139,6 +20290,8 @@ Error was thrown.
"Emulation.getOverriddenSensorInformation": Emulation.getOverriddenSensorInformationParameters; "Emulation.getOverriddenSensorInformation": Emulation.getOverriddenSensorInformationParameters;
"Emulation.setSensorOverrideEnabled": Emulation.setSensorOverrideEnabledParameters; "Emulation.setSensorOverrideEnabled": Emulation.setSensorOverrideEnabledParameters;
"Emulation.setSensorOverrideReadings": Emulation.setSensorOverrideReadingsParameters; "Emulation.setSensorOverrideReadings": Emulation.setSensorOverrideReadingsParameters;
"Emulation.setPressureSourceOverrideEnabled": Emulation.setPressureSourceOverrideEnabledParameters;
"Emulation.setPressureStateOverride": Emulation.setPressureStateOverrideParameters;
"Emulation.setIdleOverride": Emulation.setIdleOverrideParameters; "Emulation.setIdleOverride": Emulation.setIdleOverrideParameters;
"Emulation.clearIdleOverride": Emulation.clearIdleOverrideParameters; "Emulation.clearIdleOverride": Emulation.clearIdleOverrideParameters;
"Emulation.setNavigatorOverrides": Emulation.setNavigatorOverridesParameters; "Emulation.setNavigatorOverrides": Emulation.setNavigatorOverridesParameters;
@ -20159,6 +20312,7 @@ Error was thrown.
"IO.close": IO.closeParameters; "IO.close": IO.closeParameters;
"IO.read": IO.readParameters; "IO.read": IO.readParameters;
"IO.resolveBlob": IO.resolveBlobParameters; "IO.resolveBlob": IO.resolveBlobParameters;
"FileSystem.getDirectory": FileSystem.getDirectoryParameters;
"IndexedDB.clearObjectStore": IndexedDB.clearObjectStoreParameters; "IndexedDB.clearObjectStore": IndexedDB.clearObjectStoreParameters;
"IndexedDB.deleteDatabase": IndexedDB.deleteDatabaseParameters; "IndexedDB.deleteDatabase": IndexedDB.deleteDatabaseParameters;
"IndexedDB.deleteObjectStoreEntries": IndexedDB.deleteObjectStoreEntriesParameters; "IndexedDB.deleteObjectStoreEntries": IndexedDB.deleteObjectStoreEntriesParameters;
@ -20461,6 +20615,7 @@ Error was thrown.
"PWA.launch": PWA.launchParameters; "PWA.launch": PWA.launchParameters;
"PWA.launchFilesInApp": PWA.launchFilesInAppParameters; "PWA.launchFilesInApp": PWA.launchFilesInAppParameters;
"PWA.openCurrentPageInApp": PWA.openCurrentPageInAppParameters; "PWA.openCurrentPageInApp": PWA.openCurrentPageInAppParameters;
"PWA.changeAppUserSettings": PWA.changeAppUserSettingsParameters;
"Console.clearMessages": Console.clearMessagesParameters; "Console.clearMessages": Console.clearMessagesParameters;
"Console.disable": Console.disableParameters; "Console.disable": Console.disableParameters;
"Console.enable": Console.enableParameters; "Console.enable": Console.enableParameters;
@ -20735,6 +20890,8 @@ Error was thrown.
"Emulation.getOverriddenSensorInformation": Emulation.getOverriddenSensorInformationReturnValue; "Emulation.getOverriddenSensorInformation": Emulation.getOverriddenSensorInformationReturnValue;
"Emulation.setSensorOverrideEnabled": Emulation.setSensorOverrideEnabledReturnValue; "Emulation.setSensorOverrideEnabled": Emulation.setSensorOverrideEnabledReturnValue;
"Emulation.setSensorOverrideReadings": Emulation.setSensorOverrideReadingsReturnValue; "Emulation.setSensorOverrideReadings": Emulation.setSensorOverrideReadingsReturnValue;
"Emulation.setPressureSourceOverrideEnabled": Emulation.setPressureSourceOverrideEnabledReturnValue;
"Emulation.setPressureStateOverride": Emulation.setPressureStateOverrideReturnValue;
"Emulation.setIdleOverride": Emulation.setIdleOverrideReturnValue; "Emulation.setIdleOverride": Emulation.setIdleOverrideReturnValue;
"Emulation.clearIdleOverride": Emulation.clearIdleOverrideReturnValue; "Emulation.clearIdleOverride": Emulation.clearIdleOverrideReturnValue;
"Emulation.setNavigatorOverrides": Emulation.setNavigatorOverridesReturnValue; "Emulation.setNavigatorOverrides": Emulation.setNavigatorOverridesReturnValue;
@ -20755,6 +20912,7 @@ Error was thrown.
"IO.close": IO.closeReturnValue; "IO.close": IO.closeReturnValue;
"IO.read": IO.readReturnValue; "IO.read": IO.readReturnValue;
"IO.resolveBlob": IO.resolveBlobReturnValue; "IO.resolveBlob": IO.resolveBlobReturnValue;
"FileSystem.getDirectory": FileSystem.getDirectoryReturnValue;
"IndexedDB.clearObjectStore": IndexedDB.clearObjectStoreReturnValue; "IndexedDB.clearObjectStore": IndexedDB.clearObjectStoreReturnValue;
"IndexedDB.deleteDatabase": IndexedDB.deleteDatabaseReturnValue; "IndexedDB.deleteDatabase": IndexedDB.deleteDatabaseReturnValue;
"IndexedDB.deleteObjectStoreEntries": IndexedDB.deleteObjectStoreEntriesReturnValue; "IndexedDB.deleteObjectStoreEntries": IndexedDB.deleteObjectStoreEntriesReturnValue;
@ -21057,6 +21215,7 @@ Error was thrown.
"PWA.launch": PWA.launchReturnValue; "PWA.launch": PWA.launchReturnValue;
"PWA.launchFilesInApp": PWA.launchFilesInAppReturnValue; "PWA.launchFilesInApp": PWA.launchFilesInAppReturnValue;
"PWA.openCurrentPageInApp": PWA.openCurrentPageInAppReturnValue; "PWA.openCurrentPageInApp": PWA.openCurrentPageInAppReturnValue;
"PWA.changeAppUserSettings": PWA.changeAppUserSettingsReturnValue;
"Console.clearMessages": Console.clearMessagesReturnValue; "Console.clearMessages": Console.clearMessagesReturnValue;
"Console.disable": Console.disableReturnValue; "Console.disable": Console.disableReturnValue;
"Console.enable": Console.enableReturnValue; "Console.enable": Console.enableReturnValue;

View file

@ -1,6 +1,6 @@
{ {
"Blackberry PlayBook": { "Blackberry PlayBook": {
"userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/17.4 Safari/536.2+", "userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/18.0 Safari/536.2+",
"viewport": { "viewport": {
"width": 600, "width": 600,
"height": 1024 "height": 1024
@ -11,7 +11,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Blackberry PlayBook landscape": { "Blackberry PlayBook landscape": {
"userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/17.4 Safari/536.2+", "userAgent": "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/18.0 Safari/536.2+",
"viewport": { "viewport": {
"width": 1024, "width": 1024,
"height": 600 "height": 600
@ -22,7 +22,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"BlackBerry Z30": { "BlackBerry Z30": {
"userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/17.4 Mobile Safari/537.10+", "userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/18.0 Mobile Safari/537.10+",
"viewport": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -33,7 +33,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"BlackBerry Z30 landscape": { "BlackBerry Z30 landscape": {
"userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/17.4 Mobile Safari/537.10+", "userAgent": "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/18.0 Mobile Safari/537.10+",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -44,7 +44,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Galaxy Note 3": { "Galaxy Note 3": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/17.4 Mobile Safari/534.30", "userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.0 Mobile Safari/534.30",
"viewport": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -55,7 +55,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Galaxy Note 3 landscape": { "Galaxy Note 3 landscape": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/17.4 Mobile Safari/534.30", "userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.0 Mobile Safari/534.30",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -66,7 +66,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Galaxy Note II": { "Galaxy Note II": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/17.4 Mobile Safari/534.30", "userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.0 Mobile Safari/534.30",
"viewport": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -77,7 +77,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Galaxy Note II landscape": { "Galaxy Note II landscape": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/17.4 Mobile Safari/534.30", "userAgent": "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.0 Mobile Safari/534.30",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -88,7 +88,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Galaxy S III": { "Galaxy S III": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/17.4 Mobile Safari/534.30", "userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.0 Mobile Safari/534.30",
"viewport": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -99,7 +99,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Galaxy S III landscape": { "Galaxy S III landscape": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/17.4 Mobile Safari/534.30", "userAgent": "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/18.0 Mobile Safari/534.30",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -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/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 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/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 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/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 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/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 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/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 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/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 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/127.0.6533.57 Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 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/127.0.6533.57 Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Safari/537.36",
"viewport": { "viewport": {
"width": 1138, "width": 1138,
"height": 712 "height": 712
@ -198,7 +198,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"iPad (gen 5)": { "iPad (gen 5)": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"viewport": { "viewport": {
"width": 768, "width": 768,
"height": 1024 "height": 1024
@ -209,7 +209,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad (gen 5) landscape": { "iPad (gen 5) landscape": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"viewport": { "viewport": {
"width": 1024, "width": 1024,
"height": 768 "height": 768
@ -220,7 +220,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad (gen 6)": { "iPad (gen 6)": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"viewport": { "viewport": {
"width": 768, "width": 768,
"height": 1024 "height": 1024
@ -231,7 +231,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad (gen 6) landscape": { "iPad (gen 6) landscape": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"viewport": { "viewport": {
"width": 1024, "width": 1024,
"height": 768 "height": 768
@ -242,7 +242,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad (gen 7)": { "iPad (gen 7)": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"viewport": { "viewport": {
"width": 810, "width": 810,
"height": 1080 "height": 1080
@ -253,7 +253,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad (gen 7) landscape": { "iPad (gen 7) landscape": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"viewport": { "viewport": {
"width": 1080, "width": 1080,
"height": 810 "height": 810
@ -264,7 +264,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad Mini": { "iPad Mini": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"viewport": { "viewport": {
"width": 768, "width": 768,
"height": 1024 "height": 1024
@ -275,7 +275,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad Mini landscape": { "iPad Mini landscape": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"viewport": { "viewport": {
"width": 1024, "width": 1024,
"height": 768 "height": 768
@ -286,7 +286,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad Pro 11": { "iPad Pro 11": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"viewport": { "viewport": {
"width": 834, "width": 834,
"height": 1194 "height": 1194
@ -297,7 +297,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPad Pro 11 landscape": { "iPad Pro 11 landscape": {
"userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"viewport": { "viewport": {
"width": 1194, "width": 1194,
"height": 834 "height": 834
@ -308,7 +308,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 6": { "iPhone 6": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 375, "width": 375,
"height": 667 "height": 667
@ -319,7 +319,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 6 landscape": { "iPhone 6 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 667, "width": 667,
"height": 375 "height": 375
@ -330,7 +330,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 6 Plus": { "iPhone 6 Plus": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 414, "width": 414,
"height": 736 "height": 736
@ -341,7 +341,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 6 Plus landscape": { "iPhone 6 Plus landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 736, "width": 736,
"height": 414 "height": 414
@ -352,7 +352,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 7": { "iPhone 7": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 375, "width": 375,
"height": 667 "height": 667
@ -363,7 +363,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 7 landscape": { "iPhone 7 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 667, "width": 667,
"height": 375 "height": 375
@ -374,7 +374,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 7 Plus": { "iPhone 7 Plus": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 414, "width": 414,
"height": 736 "height": 736
@ -385,7 +385,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 7 Plus landscape": { "iPhone 7 Plus landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 736, "width": 736,
"height": 414 "height": 414
@ -396,7 +396,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 8": { "iPhone 8": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 375, "width": 375,
"height": 667 "height": 667
@ -407,7 +407,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 8 landscape": { "iPhone 8 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 667, "width": 667,
"height": 375 "height": 375
@ -418,7 +418,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 8 Plus": { "iPhone 8 Plus": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 414, "width": 414,
"height": 736 "height": 736
@ -429,7 +429,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 8 Plus landscape": { "iPhone 8 Plus landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 736, "width": 736,
"height": 414 "height": 414
@ -440,7 +440,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone SE": { "iPhone SE": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/17.4 Mobile/14E304 Safari/602.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/18.0 Mobile/14E304 Safari/602.1",
"viewport": { "viewport": {
"width": 320, "width": 320,
"height": 568 "height": 568
@ -451,7 +451,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone SE landscape": { "iPhone SE landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/17.4 Mobile/14E304 Safari/602.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/18.0 Mobile/14E304 Safari/602.1",
"viewport": { "viewport": {
"width": 568, "width": 568,
"height": 320 "height": 320
@ -462,7 +462,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone X": { "iPhone X": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -473,7 +473,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone X landscape": { "iPhone X landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/17.4 Mobile/15A372 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/18.0 Mobile/15A372 Safari/604.1",
"viewport": { "viewport": {
"width": 812, "width": 812,
"height": 375 "height": 375
@ -484,7 +484,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone XR": { "iPhone XR": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"viewport": { "viewport": {
"width": 414, "width": 414,
"height": 896 "height": 896
@ -495,7 +495,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone XR landscape": { "iPhone XR landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"viewport": { "viewport": {
"width": 896, "width": 896,
"height": 414 "height": 414
@ -506,7 +506,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 11": { "iPhone 11": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 414, "width": 414,
"height": 896 "height": 896
@ -521,7 +521,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 11 landscape": { "iPhone 11 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 414, "width": 414,
"height": 896 "height": 896
@ -536,7 +536,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 11 Pro": { "iPhone 11 Pro": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -551,7 +551,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 11 Pro landscape": { "iPhone 11 Pro landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -566,7 +566,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 11 Pro Max": { "iPhone 11 Pro Max": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 414, "width": 414,
"height": 896 "height": 896
@ -581,7 +581,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 11 Pro Max landscape": { "iPhone 11 Pro Max landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 414, "width": 414,
"height": 896 "height": 896
@ -596,7 +596,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12": { "iPhone 12": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -611,7 +611,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 landscape": { "iPhone 12 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -626,7 +626,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 Pro": { "iPhone 12 Pro": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -641,7 +641,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 Pro landscape": { "iPhone 12 Pro landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -656,7 +656,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 Pro Max": { "iPhone 12 Pro Max": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 428, "width": 428,
"height": 926 "height": 926
@ -671,7 +671,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 Pro Max landscape": { "iPhone 12 Pro Max landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 428, "width": 428,
"height": 926 "height": 926
@ -686,7 +686,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 Mini": { "iPhone 12 Mini": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -701,7 +701,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 12 Mini landscape": { "iPhone 12 Mini landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -716,7 +716,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13": { "iPhone 13": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -731,7 +731,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 landscape": { "iPhone 13 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -746,7 +746,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 Pro": { "iPhone 13 Pro": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -761,7 +761,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 Pro landscape": { "iPhone 13 Pro landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -776,7 +776,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 Pro Max": { "iPhone 13 Pro Max": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 428, "width": 428,
"height": 926 "height": 926
@ -791,7 +791,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 Pro Max landscape": { "iPhone 13 Pro Max landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 428, "width": 428,
"height": 926 "height": 926
@ -806,7 +806,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 Mini": { "iPhone 13 Mini": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -821,7 +821,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 13 Mini landscape": { "iPhone 13 Mini landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 375, "width": 375,
"height": 812 "height": 812
@ -836,7 +836,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 14": { "iPhone 14": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -851,7 +851,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 14 landscape": { "iPhone 14 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 390, "width": 390,
"height": 844 "height": 844
@ -866,7 +866,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 14 Plus": { "iPhone 14 Plus": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 428, "width": 428,
"height": 926 "height": 926
@ -881,7 +881,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 14 Plus landscape": { "iPhone 14 Plus landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 428, "width": 428,
"height": 926 "height": 926
@ -896,7 +896,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 14 Pro": { "iPhone 14 Pro": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 393, "width": 393,
"height": 852 "height": 852
@ -911,7 +911,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 14 Pro landscape": { "iPhone 14 Pro landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 393, "width": 393,
"height": 852 "height": 852
@ -926,7 +926,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 14 Pro Max": { "iPhone 14 Pro Max": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 430, "width": 430,
"height": 932 "height": 932
@ -941,7 +941,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 14 Pro Max landscape": { "iPhone 14 Pro Max landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 430, "width": 430,
"height": 932 "height": 932
@ -956,7 +956,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 15": { "iPhone 15": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 393, "width": 393,
"height": 852 "height": 852
@ -971,7 +971,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 15 landscape": { "iPhone 15 landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 393, "width": 393,
"height": 852 "height": 852
@ -986,7 +986,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 15 Plus": { "iPhone 15 Plus": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 430, "width": 430,
"height": 932 "height": 932
@ -1001,7 +1001,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 15 Plus landscape": { "iPhone 15 Plus landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 430, "width": 430,
"height": 932 "height": 932
@ -1016,7 +1016,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 15 Pro": { "iPhone 15 Pro": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 393, "width": 393,
"height": 852 "height": 852
@ -1031,7 +1031,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 15 Pro landscape": { "iPhone 15 Pro landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 393, "width": 393,
"height": 852 "height": 852
@ -1046,7 +1046,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 15 Pro Max": { "iPhone 15 Pro Max": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 430, "width": 430,
"height": 932 "height": 932
@ -1061,7 +1061,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"iPhone 15 Pro Max landscape": { "iPhone 15 Pro Max landscape": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"screen": { "screen": {
"width": 430, "width": 430,
"height": 932 "height": 932
@ -1098,7 +1098,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"LG Optimus L70": { "LG Optimus L70": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 384, "width": 384,
"height": 640 "height": 640
@ -1109,7 +1109,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"LG Optimus L70 landscape": { "LG Optimus L70 landscape": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 384 "height": 384
@ -1120,7 +1120,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Microsoft Lumia 550": { "Microsoft Lumia 550": {
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36 Edge/14.14263",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -1131,7 +1131,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Microsoft Lumia 550 landscape": { "Microsoft Lumia 550 landscape": {
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36 Edge/14.14263",
"viewport": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -1142,7 +1142,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Microsoft Lumia 950": { "Microsoft Lumia 950": {
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36 Edge/14.14263",
"viewport": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -1153,7 +1153,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Microsoft Lumia 950 landscape": { "Microsoft Lumia 950 landscape": {
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36 Edge/14.14263",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -1164,7 +1164,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 10": { "Nexus 10": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Safari/537.36",
"viewport": { "viewport": {
"width": 800, "width": 800,
"height": 1280 "height": 1280
@ -1175,7 +1175,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 10 landscape": { "Nexus 10 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Safari/537.36",
"viewport": { "viewport": {
"width": 1280, "width": 1280,
"height": 800 "height": 800
@ -1186,7 +1186,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 4": { "Nexus 4": {
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 384, "width": 384,
"height": 640 "height": 640
@ -1197,7 +1197,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 4 landscape": { "Nexus 4 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 384 "height": 384
@ -1208,7 +1208,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 5": { "Nexus 5": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -1219,7 +1219,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 5 landscape": { "Nexus 5 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -1230,7 +1230,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 5X": { "Nexus 5X": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 412, "width": 412,
"height": 732 "height": 732
@ -1241,7 +1241,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 5X landscape": { "Nexus 5X landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 732, "width": 732,
"height": 412 "height": 412
@ -1252,7 +1252,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 6": { "Nexus 6": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 412, "width": 412,
"height": 732 "height": 732
@ -1263,7 +1263,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 6 landscape": { "Nexus 6 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 732, "width": 732,
"height": 412 "height": 412
@ -1274,7 +1274,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 6P": { "Nexus 6P": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 412, "width": 412,
"height": 732 "height": 732
@ -1285,7 +1285,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 6P landscape": { "Nexus 6P landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 732, "width": 732,
"height": 412 "height": 412
@ -1296,7 +1296,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 7": { "Nexus 7": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Safari/537.36",
"viewport": { "viewport": {
"width": 600, "width": 600,
"height": 960 "height": 960
@ -1307,7 +1307,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Nexus 7 landscape": { "Nexus 7 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Safari/537.36",
"viewport": { "viewport": {
"width": 960, "width": 960,
"height": 600 "height": 600
@ -1362,7 +1362,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Pixel 2": { "Pixel 2": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 411, "width": 411,
"height": 731 "height": 731
@ -1373,7 +1373,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Pixel 2 landscape": { "Pixel 2 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 731, "width": 731,
"height": 411 "height": 411
@ -1384,7 +1384,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Pixel 2 XL": { "Pixel 2 XL": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 411, "width": 411,
"height": 823 "height": 823
@ -1395,7 +1395,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Pixel 2 XL landscape": { "Pixel 2 XL landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 823, "width": 823,
"height": 411 "height": 411
@ -1406,7 +1406,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Pixel 3": { "Pixel 3": {
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 393, "width": 393,
"height": 786 "height": 786
@ -1417,7 +1417,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Pixel 3 landscape": { "Pixel 3 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 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/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 786, "width": 786,
"height": 393 "height": 393
@ -1428,7 +1428,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Pixel 4": { "Pixel 4": {
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 353, "width": 353,
"height": 745 "height": 745
@ -1439,7 +1439,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Pixel 4 landscape": { "Pixel 4 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 745, "width": 745,
"height": 353 "height": 353
@ -1450,7 +1450,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Pixel 4a (5G)": { "Pixel 4a (5G)": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"screen": { "screen": {
"width": 412, "width": 412,
"height": 892 "height": 892
@ -1465,7 +1465,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Pixel 4a (5G) landscape": { "Pixel 4a (5G) landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"screen": { "screen": {
"height": 892, "height": 892,
"width": 412 "width": 412
@ -1480,7 +1480,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Pixel 5": { "Pixel 5": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"screen": { "screen": {
"width": 393, "width": 393,
"height": 851 "height": 851
@ -1495,7 +1495,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Pixel 5 landscape": { "Pixel 5 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"screen": { "screen": {
"width": 851, "width": 851,
"height": 393 "height": 393
@ -1510,7 +1510,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Pixel 7": { "Pixel 7": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"screen": { "screen": {
"width": 412, "width": 412,
"height": 915 "height": 915
@ -1525,7 +1525,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Pixel 7 landscape": { "Pixel 7 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"screen": { "screen": {
"width": 915, "width": 915,
"height": 412 "height": 412
@ -1540,7 +1540,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Moto G4": { "Moto G4": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 360, "width": 360,
"height": 640 "height": 640
@ -1551,7 +1551,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Moto G4 landscape": { "Moto G4 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Mobile Safari/537.36", "userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Mobile Safari/537.36",
"viewport": { "viewport": {
"width": 640, "width": 640,
"height": 360 "height": 360
@ -1562,7 +1562,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Desktop Chrome HiDPI": { "Desktop Chrome HiDPI": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Safari/537.36", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Safari/537.36",
"screen": { "screen": {
"width": 1792, "width": 1792,
"height": 1120 "height": 1120
@ -1577,7 +1577,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Desktop Edge HiDPI": { "Desktop Edge HiDPI": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Safari/537.36 Edg/127.0.6533.57", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Safari/537.36 Edg/128.0.6613.7",
"screen": { "screen": {
"width": 1792, "width": 1792,
"height": 1120 "height": 1120
@ -1607,7 +1607,7 @@
"defaultBrowserType": "firefox" "defaultBrowserType": "firefox"
}, },
"Desktop Safari": { "Desktop Safari": {
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15", "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",
"screen": { "screen": {
"width": 1792, "width": 1792,
"height": 1120 "height": 1120
@ -1622,7 +1622,7 @@
"defaultBrowserType": "webkit" "defaultBrowserType": "webkit"
}, },
"Desktop Chrome": { "Desktop Chrome": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Safari/537.36", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Safari/537.36",
"screen": { "screen": {
"width": 1920, "width": 1920,
"height": 1080 "height": 1080
@ -1637,7 +1637,7 @@
"defaultBrowserType": "chromium" "defaultBrowserType": "chromium"
}, },
"Desktop Edge": { "Desktop Edge": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.57 Safari/537.36 Edg/127.0.6533.57", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.7 Safari/537.36 Edg/128.0.6613.7",
"screen": { "screen": {
"width": 1920, "width": 1920,
"height": 1080 "height": 1080

View file

@ -230,6 +230,9 @@ class RecordActionTool implements RecorderTool {
// So we check the hovered element instead, and if it is a range input, we skip click handling // So we check the hovered element instead, and if it is a range input, we skip click handling
if (isRangeInput(this._hoveredElement)) if (isRangeInput(this._hoveredElement))
return; return;
// Right clicks are handled by 'contextmenu' event if its auxclick
if (event.button === 2 && event.type === 'auxclick')
return;
if (this._shouldIgnoreMouseEvent(event)) if (this._shouldIgnoreMouseEvent(event))
return; return;
if (this._actionInProgress(event)) if (this._actionInProgress(event))

View file

@ -16,17 +16,28 @@
import net from 'net'; import net from 'net';
import path from 'path'; import path from 'path';
import http2 from 'http2';
import type https from 'https'; import type https from 'https';
import fs from 'fs'; import fs from 'fs';
import tls from 'tls'; import tls from 'tls';
import stream from 'stream'; import stream from 'stream';
import { createSocket } from '../utils/happy-eyeballs'; import { createSocket, createTLSSocket } from '../utils/happy-eyeballs';
import { globToRegex, isUnderTest, ManualPromise } from '../utils'; import { isUnderTest, ManualPromise } from '../utils';
import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../common/socksProxy'; import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../common/socksProxy';
import { SocksProxy } from '../common/socksProxy'; import { SocksProxy } from '../common/socksProxy';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
let dummyServerTlsOptions: tls.TlsOptions | undefined = undefined;
function loadDummyServerCertsIfNeeded() {
if (dummyServerTlsOptions)
return;
dummyServerTlsOptions = {
key: fs.readFileSync(path.join(__dirname, '../../bin/socks-certs/key.pem')),
cert: fs.readFileSync(path.join(__dirname, '../../bin/socks-certs/cert.pem')),
};
}
class ALPNCache { class ALPNCache {
private _cache = new Map<string, ManualPromise<string>>(); private _cache = new Map<string, ManualPromise<string>>();
@ -42,22 +53,21 @@ class ALPNCache {
const result = new ManualPromise<string>(); const result = new ManualPromise<string>();
this._cache.set(cacheKey, result); this._cache.set(cacheKey, result);
result.then(success); result.then(success);
const socket = tls.connect({ createTLSSocket({
host, host,
port, port,
servername: net.isIP(host) ? undefined : host, servername: net.isIP(host) ? undefined : host,
ALPNProtocols: ['h2', 'http/1.1'], ALPNProtocols: ['h2', 'http/1.1'],
rejectUnauthorized: false, rejectUnauthorized: false,
}); }).then(socket => {
socket.on('secureConnect', () => { socket.on('secureConnect', () => {
// The server may not respond with ALPN, in which case we default to http/1.1. // The server may not respond with ALPN, in which case we default to http/1.1.
result.resolve(socket.alpnProtocol || 'http/1.1'); result.resolve(socket.alpnProtocol || 'http/1.1');
socket.end(); socket.end();
}); });
socket.on('error', error => { }).catch(error => {
debugLogger.log('client-certificates', `ALPN error: ${error.message}`); debugLogger.log('client-certificates', `ALPN error: ${error.message}`);
result.resolve('http/1.1'); result.resolve('http/1.1');
socket.end();
}); });
} }
} }
@ -71,17 +81,19 @@ class SocksProxyConnection {
target!: net.Socket; target!: net.Socket;
// In case of http, we just pipe data to the target socket and they are |undefined|. // In case of http, we just pipe data to the target socket and they are |undefined|.
internal: stream.Duplex | undefined; internal: stream.Duplex | undefined;
private _targetCloseEventListener: () => void;
constructor(socksProxy: ClientCertificatesProxy, uid: string, host: string, port: number) { constructor(socksProxy: ClientCertificatesProxy, uid: string, host: string, port: number) {
this.socksProxy = socksProxy; this.socksProxy = socksProxy;
this.uid = uid; this.uid = uid;
this.host = host; this.host = host;
this.port = port; this.port = port;
this._targetCloseEventListener = () => this.socksProxy._socksProxy.sendSocketEnd({ uid: this.uid });
} }
async connect() { async connect() {
this.target = await createSocket(rewriteToLocalhostIfNeeded(this.host), this.port); this.target = await createSocket(rewriteToLocalhostIfNeeded(this.host), this.port);
this.target.on('close', () => this.socksProxy._socksProxy.sendSocketEnd({ uid: this.uid })); this.target.on('close', this._targetCloseEventListener);
this.target.on('error', error => this.socksProxy._socksProxy.sendSocketError({ uid: this.uid, error: error.message })); this.target.on('error', error => this.socksProxy._socksProxy.sendSocketError({ uid: this.uid, error: error.message }));
this.socksProxy._socksProxy.socketConnected({ this.socksProxy._socksProxy.socketConnected({
uid: this.uid, uid: this.uid,
@ -123,15 +135,13 @@ class SocksProxyConnection {
this.socksProxy.alpnCache.get(rewriteToLocalhostIfNeeded(this.host), this.port, alpnProtocolChosenByServer => { this.socksProxy.alpnCache.get(rewriteToLocalhostIfNeeded(this.host), this.port, alpnProtocolChosenByServer => {
debugLogger.log('client-certificates', `Proxy->Target ${this.host}:${this.port} chooses ALPN ${alpnProtocolChosenByServer}`); debugLogger.log('client-certificates', `Proxy->Target ${this.host}:${this.port} chooses ALPN ${alpnProtocolChosenByServer}`);
const dummyServer = tls.createServer({ const dummyServer = tls.createServer({
key: fs.readFileSync(path.join(__dirname, '../../bin/socks-certs/key.pem')), ...dummyServerTlsOptions,
cert: fs.readFileSync(path.join(__dirname, '../../bin/socks-certs/cert.pem')),
ALPNProtocols: alpnProtocolChosenByServer === 'h2' ? ['h2', 'http/1.1'] : ['http/1.1'], ALPNProtocols: alpnProtocolChosenByServer === 'h2' ? ['h2', 'http/1.1'] : ['http/1.1'],
}); });
this.internal?.on('close', () => dummyServer.close()); this.internal?.on('close', () => dummyServer.close());
dummyServer.emit('connection', this.internal); dummyServer.emit('connection', this.internal);
dummyServer.on('secureConnection', internalTLS => { dummyServer.on('secureConnection', internalTLS => {
debugLogger.log('client-certificates', `Browser->Proxy ${this.host}:${this.port} chooses ALPN ${internalTLS.alpnProtocol}`); debugLogger.log('client-certificates', `Browser->Proxy ${this.host}:${this.port} chooses ALPN ${internalTLS.alpnProtocol}`);
internalTLS.on('close', () => this.socksProxy._socksProxy.sendSocketEnd({ uid: this.uid }));
const tlsOptions: tls.ConnectionOptions = { const tlsOptions: tls.ConnectionOptions = {
socket: this.target, socket: this.target,
host: this.host, host: this.host,
@ -146,8 +156,10 @@ class SocksProxyConnection {
tlsOptions.ca = [fs.readFileSync(process.env.PWTEST_UNSUPPORTED_CUSTOM_CA)]; tlsOptions.ca = [fs.readFileSync(process.env.PWTEST_UNSUPPORTED_CUSTOM_CA)];
const targetTLS = tls.connect(tlsOptions); const targetTLS = tls.connect(tlsOptions);
targetTLS.on('secureConnect', () => {
internalTLS.pipe(targetTLS); internalTLS.pipe(targetTLS);
targetTLS.pipe(internalTLS); targetTLS.pipe(internalTLS);
});
// Handle close and errors // Handle close and errors
const closeBothSockets = () => { const closeBothSockets = () => {
@ -161,11 +173,30 @@ class SocksProxyConnection {
internalTLS.on('error', () => closeBothSockets()); internalTLS.on('error', () => closeBothSockets());
targetTLS.on('error', error => { targetTLS.on('error', error => {
debugLogger.log('client-certificates', `error when connecting to target: ${error.message}`); debugLogger.log('client-certificates', `error when connecting to target: ${error.message}`);
if (internalTLS?.alpnProtocol === 'h2') {
// https://github.com/nodejs/node/issues/46152
// TODO: http2.performServerHandshake does not work here for some reason.
} else {
const responseBody = 'Playwright client-certificate error: ' + error.message; const responseBody = 'Playwright client-certificate error: ' + error.message;
if (internalTLS?.alpnProtocol === 'h2') {
// This method is available only in Node.js 20+
if ('performServerHandshake' in http2) {
// In case of an 'error' event on the target connection, we still need to perform the http2 handshake on the browser side.
// This is an async operation, so we need to intercept the close event to prevent the socket from being closed too early.
this.target.removeListener('close', this._targetCloseEventListener);
// @ts-expect-error
const session: http2.ServerHttp2Session = http2.performServerHandshake(internalTLS);
session.on('stream', (stream: http2.ServerHttp2Stream) => {
stream.respond({
'content-type': 'text/html',
[http2.constants.HTTP2_HEADER_STATUS]: 503,
});
stream.end(responseBody, () => {
session.close();
closeBothSockets();
});
stream.on('error', () => closeBothSockets());
});
} else {
closeBothSockets();
}
} else {
internalTLS.end([ internalTLS.end([
'HTTP/1.1 503 Internal Server Error', 'HTTP/1.1 503 Internal Server Error',
'Content-Type: text/html; charset=utf-8', 'Content-Type: text/html; charset=utf-8',
@ -173,8 +204,8 @@ class SocksProxyConnection {
'\r\n', '\r\n',
responseBody, responseBody,
].join('\r\n')); ].join('\r\n'));
}
closeBothSockets(); closeBothSockets();
}
}); });
}); });
}); });
@ -212,6 +243,7 @@ export class ClientCertificatesProxy {
this._connections.get(payload.uid)?.onClose(); this._connections.get(payload.uid)?.onClose();
this._connections.delete(payload.uid); this._connections.delete(payload.uid);
}); });
loadDummyServerCertsIfNeeded();
} }
public async listen(): Promise<string> { public async listen(): Promise<string> {
@ -224,20 +256,16 @@ export class ClientCertificatesProxy {
} }
} }
const kClientCertificatesGlobRegex = Symbol('kClientCertificatesGlobRegex');
export function clientCertificatesToTLSOptions( export function clientCertificatesToTLSOptions(
clientCertificates: channels.BrowserNewContextOptions['clientCertificates'], clientCertificates: channels.BrowserNewContextOptions['clientCertificates'],
origin: string origin: string
): Pick<https.RequestOptions, 'pfx' | 'key' | 'cert'> | undefined { ): Pick<https.RequestOptions, 'pfx' | 'key' | 'cert'> | undefined {
const matchingCerts = clientCertificates?.filter(c => { const matchingCerts = clientCertificates?.filter(c => {
let regex: RegExp | undefined = (c as any)[kClientCertificatesGlobRegex]; try {
if (!regex) { return new URL(c.origin).origin === origin;
regex = globToRegex(c.origin); } catch (error) {
(c as any)[kClientCertificatesGlobRegex] = regex; return c.origin === origin;
} }
regex.lastIndex = 0;
return regex.test(origin);
}); });
if (!matchingCerts || !matchingCerts.length) if (!matchingCerts || !matchingCerts.length)
return; return;

View file

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

View file

@ -20,6 +20,7 @@ import * as https from 'https';
import * as net from 'net'; import * as net from 'net';
import * as tls from 'tls'; import * as tls from 'tls';
import { ManualPromise } from './manualPromise'; import { ManualPromise } from './manualPromise';
import { assert } from './debug';
// Implementation(partial) of Happy Eyeballs 2 algorithm described in // Implementation(partial) of Happy Eyeballs 2 algorithm described in
// https://www.rfc-editor.org/rfc/rfc8305 // https://www.rfc-editor.org/rfc/rfc8305
@ -66,7 +67,41 @@ export async function createSocket(host: string, port: number): Promise<net.Sock
}); });
} }
async function createConnectionAsync(options: http.ClientRequestArgs, oncreate: ((err: Error | null, socket?: net.Socket) => void) | undefined, useTLS: boolean) { export async function createTLSSocket(options: tls.ConnectionOptions): Promise<tls.TLSSocket> {
return new Promise((resolve, reject) => {
assert(options.host, 'host is required');
if (net.isIP(options.host)) {
const socket = tls.connect(options)
socket.on('connect', () => resolve(socket));
socket.on('error', error => reject(error));
} else {
createConnectionAsync(options, (err, socket) => {
if (err)
reject(err);
if (socket)
resolve(socket);
}, true).catch(err => reject(err));
}
});
}
export async function createConnectionAsync(
options: http.ClientRequestArgs,
oncreate: ((err: Error | null, socket?: tls.TLSSocket) => void) | undefined,
useTLS: true
): Promise<void>;
export async function createConnectionAsync(
options: http.ClientRequestArgs,
oncreate: ((err: Error | null, socket?: net.Socket) => void) | undefined,
useTLS: false
): Promise<void>;
export async function createConnectionAsync(
options: http.ClientRequestArgs,
oncreate: ((err: Error | null, socket?: any) => void) | undefined,
useTLS: boolean
): Promise<void> {
const lookup = (options as any).__testHookLookup || lookupAddresses; const lookup = (options as any).__testHookLookup || lookupAddresses;
const hostname = clientRequestArgsToHostName(options); const hostname = clientRequestArgsToHostName(options);
const addresses = await lookup(hostname); const addresses = await lookup(hostname);

View file

@ -17,6 +17,7 @@
import http from 'http'; import http from 'http';
import https from 'https'; import https from 'https';
import http2 from 'http2';
import type net from 'net'; import type net from 'net';
import { getProxyForUrl } from '../utilsBundle'; import { getProxyForUrl } from '../utilsBundle';
import { HttpsProxyAgent } from '../utilsBundle'; import { HttpsProxyAgent } from '../utilsBundle';
@ -169,6 +170,14 @@ export function createHttpsServer(...args: any[]): https.Server {
return server; return server;
} }
export function createHttp2Server( onRequestHandler?: (request: http2.Http2ServerRequest, response: http2.Http2ServerResponse) => void,): http2.Http2SecureServer;
export function createHttp2Server(options: http2.SecureServerOptions, onRequestHandler?: (request: http2.Http2ServerRequest, response: http2.Http2ServerResponse) => void,): http2.Http2SecureServer;
export function createHttp2Server(...args: any[]): http2.Http2SecureServer {
const server = http2.createSecureServer(...args);
decorateServer(server);
return server;
}
export async function isURLAvailable(url: URL, ignoreHTTPSErrors: boolean, onLog?: (data: string) => void, onStdErr?: (data: string) => void) { export async function isURLAvailable(url: URL, ignoreHTTPSErrors: boolean, onLog?: (data: string) => void, onStdErr?: (data: string) => void) {
let statusCode = await httpStatusCode(url, ignoreHTTPSErrors, onLog, onStdErr); let statusCode = await httpStatusCode(url, ignoreHTTPSErrors, onLog, onStdErr);
if (statusCode === 404 && url.pathname === '/') { if (statusCode === 404 && url.pathname === '/') {
@ -200,7 +209,7 @@ async function httpStatusCode(url: URL, ignoreHTTPSErrors: boolean, onLog?: (dat
}); });
} }
function decorateServer(server: http.Server | http.Server) { function decorateServer(server: net.Server) {
const sockets = new Set<net.Socket>(); const sockets = new Set<net.Socket>();
server.on('connection', socket => { server.on('connection', socket => {
sockets.add(socket); sockets.add(socket);

View file

@ -112,7 +112,7 @@ export module Protocol {
- from 'checked' to 'selected': states which apply to widgets - from 'checked' to 'selected': states which apply to widgets
- from 'activedescendant' to 'owns' - relationships between elements other than parent/child/sibling. - from 'activedescendant' to 'owns' - relationships between elements other than parent/child/sibling.
*/ */
export type AXPropertyName = "busy"|"disabled"|"editable"|"focusable"|"focused"|"hidden"|"hiddenRoot"|"invalid"|"keyshortcuts"|"settable"|"roledescription"|"live"|"atomic"|"relevant"|"root"|"autocomplete"|"hasPopup"|"level"|"multiselectable"|"orientation"|"multiline"|"readonly"|"required"|"valuemin"|"valuemax"|"valuetext"|"checked"|"expanded"|"modal"|"pressed"|"selected"|"activedescendant"|"controls"|"describedby"|"details"|"errormessage"|"flowto"|"labelledby"|"owns"; export type AXPropertyName = "busy"|"disabled"|"editable"|"focusable"|"focused"|"hidden"|"hiddenRoot"|"invalid"|"keyshortcuts"|"settable"|"roledescription"|"live"|"atomic"|"relevant"|"root"|"autocomplete"|"hasPopup"|"level"|"multiselectable"|"orientation"|"multiline"|"readonly"|"required"|"valuemin"|"valuemax"|"valuetext"|"checked"|"expanded"|"modal"|"pressed"|"selected"|"activedescendant"|"controls"|"describedby"|"details"|"errormessage"|"flowto"|"labelledby"|"owns"|"url";
/** /**
* A node in the accessibility tree. * A node in the accessibility tree.
*/ */
@ -875,7 +875,7 @@ instead of "limited-quirks".
sharedDictionaryError: SharedDictionaryError; sharedDictionaryError: SharedDictionaryError;
request: AffectedRequest; request: AffectedRequest;
} }
export type GenericIssueErrorType = "CrossOriginPortalPostMessageError"|"FormLabelForNameError"|"FormDuplicateIdForInputError"|"FormInputWithNoLabelError"|"FormAutocompleteAttributeEmptyError"|"FormEmptyIdAndNameAttributesForInputError"|"FormAriaLabelledByToNonExistingId"|"FormInputAssignedAutocompleteValueToIdOrNameAttributeError"|"FormLabelHasNeitherForNorNestedInput"|"FormLabelForMatchesNonExistingIdError"|"FormInputHasWrongButWellIntendedAutocompleteValueError"|"ResponseWasBlockedByORB"; export type GenericIssueErrorType = "FormLabelForNameError"|"FormDuplicateIdForInputError"|"FormInputWithNoLabelError"|"FormAutocompleteAttributeEmptyError"|"FormEmptyIdAndNameAttributesForInputError"|"FormAriaLabelledByToNonExistingId"|"FormInputAssignedAutocompleteValueToIdOrNameAttributeError"|"FormLabelHasNeitherForNorNestedInput"|"FormLabelForMatchesNonExistingIdError"|"FormInputHasWrongButWellIntendedAutocompleteValueError"|"ResponseWasBlockedByORB";
/** /**
* Depending on the concrete errorType, different properties are set. * Depending on the concrete errorType, different properties are set.
*/ */
@ -934,7 +934,7 @@ Should be updated alongside RequestIdTokenStatus in
third_party/blink/public/mojom/devtools/inspector_issue.mojom to include third_party/blink/public/mojom/devtools/inspector_issue.mojom to include
all cases except for success. all cases except for success.
*/ */
export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"DisabledInSettings"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenIdpErrorResponse"|"IdTokenCrossSiteIdpErrorResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"|"MissingTransientUserActivation"|"ReplacedByButtonMode"|"RelyingPartyOriginIsOpaque"|"TypeNotMatching"; export type FederatedAuthRequestIssueReason = "ShouldEmbargo"|"TooManyRequests"|"WellKnownHttpNotFound"|"WellKnownNoResponse"|"WellKnownInvalidResponse"|"WellKnownListEmpty"|"WellKnownInvalidContentType"|"ConfigNotInWellKnown"|"WellKnownTooBig"|"ConfigHttpNotFound"|"ConfigNoResponse"|"ConfigInvalidResponse"|"ConfigInvalidContentType"|"ClientMetadataHttpNotFound"|"ClientMetadataNoResponse"|"ClientMetadataInvalidResponse"|"ClientMetadataInvalidContentType"|"IdpNotPotentiallyTrustworthy"|"DisabledInSettings"|"DisabledInFlags"|"ErrorFetchingSignin"|"InvalidSigninResponse"|"AccountsHttpNotFound"|"AccountsNoResponse"|"AccountsInvalidResponse"|"AccountsListEmpty"|"AccountsInvalidContentType"|"IdTokenHttpNotFound"|"IdTokenNoResponse"|"IdTokenInvalidResponse"|"IdTokenIdpErrorResponse"|"IdTokenCrossSiteIdpErrorResponse"|"IdTokenInvalidRequest"|"IdTokenInvalidContentType"|"ErrorIdToken"|"Canceled"|"RpPageNotVisible"|"SilentMediationFailure"|"ThirdPartyCookiesBlocked"|"NotSignedInWithIdp"|"MissingTransientUserActivation"|"ReplacedByButtonMode"|"InvalidFieldsSpecified"|"RelyingPartyOriginIsOpaque"|"TypeNotMatching";
export interface FederatedAuthUserInfoRequestIssueDetails { export interface FederatedAuthUserInfoRequestIssueDetails {
federatedAuthUserInfoRequestIssueReason: FederatedAuthUserInfoRequestIssueReason; federatedAuthUserInfoRequestIssueReason: FederatedAuthUserInfoRequestIssueReason;
} }
@ -1480,6 +1480,10 @@ Note that userVisibleOnly = true is the only currently supported type.
* For "clipboard" permission, may specify allowWithoutSanitization. * For "clipboard" permission, may specify allowWithoutSanitization.
*/ */
allowWithoutSanitization?: boolean; allowWithoutSanitization?: boolean;
/**
* For "fullscreen" permission, must specify allowWithoutGesture:true.
*/
allowWithoutGesture?: boolean;
/** /**
* For "camera" permission, may specify panTiltZoom. * For "camera" permission, may specify panTiltZoom.
*/ */
@ -2559,6 +2563,7 @@ stylesheet rules) this rule came from.
* Associated style declaration. * Associated style declaration.
*/ */
style: CSSStyle; style: CSSStyle;
active: boolean;
} }
/** /**
* CSS keyframes rule representation. * CSS keyframes rule representation.
@ -2888,9 +2893,14 @@ attributes) for a DOM node identified by `nodeId`.
*/ */
cssPositionFallbackRules?: CSSPositionFallbackRule[]; cssPositionFallbackRules?: CSSPositionFallbackRule[];
/** /**
* A list of CSS @position-try rules matching this node, based on the position-try-options property. * A list of CSS @position-try rules matching this node, based on the position-try-fallbacks property.
*/ */
cssPositionTryRules?: CSSPositionTryRule[]; cssPositionTryRules?: CSSPositionTryRule[];
/**
* Index of the active fallback in the applied position-try-fallback property,
will not be set if there is no active position-try fallback.
*/
activePositionFallbackIndex?: number;
/** /**
* A list of CSS at-property rules matching this node. * A list of CSS at-property rules matching this node.
*/ */
@ -5907,6 +5917,11 @@ See https://w3c.github.io/sensors/#automation for more information.
xyz?: SensorReadingXYZ; xyz?: SensorReadingXYZ;
quaternion?: SensorReadingQuaternion; quaternion?: SensorReadingQuaternion;
} }
export type PressureSource = "cpu";
export type PressureState = "nominal"|"fair"|"serious"|"critical";
export interface PressureMetadata {
available?: boolean;
}
/** /**
* Enum of image types that can be disabled. * Enum of image types that can be disabled.
*/ */
@ -6190,6 +6205,30 @@ by setSensorOverrideEnabled.
} }
export type setSensorOverrideReadingsReturnValue = { export type setSensorOverrideReadingsReturnValue = {
} }
/**
* Overrides a pressure source of a given type, as used by the Compute
Pressure API, so that updates to PressureObserver.observe() are provided
via setPressureStateOverride instead of being retrieved from
platform-provided telemetry data.
*/
export type setPressureSourceOverrideEnabledParameters = {
enabled: boolean;
source: PressureSource;
metadata?: PressureMetadata;
}
export type setPressureSourceOverrideEnabledReturnValue = {
}
/**
* Provides a given pressure state that will be processed and eventually be
delivered to PressureObserver users. |source| must have been previously
overridden by setPressureSourceOverrideEnabled.
*/
export type setPressureStateOverrideParameters = {
source: PressureSource;
state: PressureState;
}
export type setPressureStateOverrideReturnValue = {
}
/** /**
* Overrides the Idle state. * Overrides the Idle state.
*/ */
@ -6533,6 +6572,54 @@ following the last read). Some types of streams may only support sequential read
} }
} }
export module FileSystem {
export interface File {
name: string;
/**
* Timestamp
*/
lastModified: Network.TimeSinceEpoch;
/**
* Size in bytes
*/
size: number;
type: string;
}
export interface Directory {
name: string;
nestedDirectories: string[];
/**
* Files that are directly nested under this directory.
*/
nestedFiles: File[];
}
export interface BucketFileSystemLocator {
/**
* Storage key
*/
storageKey: Storage.SerializedStorageKey;
/**
* Bucket name. Not passing a `bucketName` will retrieve the default Bucket. (https://developer.mozilla.org/en-US/docs/Web/API/Storage_API#storage_buckets)
*/
bucketName?: string;
/**
* Path to the directory using each path component as an array item.
*/
pathComponents: string[];
}
export type getDirectoryParameters = {
bucketFileSystemLocator: BucketFileSystemLocator;
}
export type getDirectoryReturnValue = {
/**
* Returns the directory object at the path.
*/
directory: Directory;
}
}
export module IndexedDB { export module IndexedDB {
/** /**
* Database with an array of object stores. * Database with an array of object stores.
@ -9048,7 +9135,7 @@ the same request (but not for redirected requests).
initiatorIPAddressSpace: IPAddressSpace; initiatorIPAddressSpace: IPAddressSpace;
privateNetworkRequestPolicy: PrivateNetworkRequestPolicy; privateNetworkRequestPolicy: PrivateNetworkRequestPolicy;
} }
export type CrossOriginOpenerPolicyValue = "SameOrigin"|"SameOriginAllowPopups"|"RestrictProperties"|"UnsafeNone"|"SameOriginPlusCoep"|"RestrictPropertiesPlusCoep"; export type CrossOriginOpenerPolicyValue = "SameOrigin"|"SameOriginAllowPopups"|"RestrictProperties"|"UnsafeNone"|"SameOriginPlusCoep"|"RestrictPropertiesPlusCoep"|"NoopenerAllowPopups";
export interface CrossOriginOpenerPolicyStatus { export interface CrossOriginOpenerPolicyStatus {
value: CrossOriginOpenerPolicyValue; value: CrossOriginOpenerPolicyValue;
reportOnlyValue: CrossOriginOpenerPolicyValue; reportOnlyValue: CrossOriginOpenerPolicyValue;
@ -9731,6 +9818,10 @@ preemptively (e.g. a cache hit).
*/ */
issuedTokenCount?: number; issuedTokenCount?: number;
} }
/**
* Fired once security policy has been updated.
*/
export type policyUpdatedPayload = void;
/** /**
* Fired once when parsing the .wbn file has succeeded. * Fired once when parsing the .wbn file has succeeded.
The event contains the information about the web bundle contents. The event contains the information about the web bundle contents.
@ -11278,7 +11369,7 @@ as an ad.
* All Permissions Policy features. This enum should match the one defined * All Permissions Policy features. This enum should match the one defined
in third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5. in third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5.
*/ */
export type PermissionsPolicyFeature = "accelerometer"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"cross-origin-isolated"|"deferred-fetch"|"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"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking"; export type PermissionsPolicyFeature = "accelerometer"|"ambient-light-sensor"|"attribution-reporting"|"autoplay"|"bluetooth"|"browsing-topics"|"camera"|"captured-surface-control"|"ch-dpr"|"ch-device-memory"|"ch-downlink"|"ch-ect"|"ch-prefers-color-scheme"|"ch-prefers-reduced-motion"|"ch-prefers-reduced-transparency"|"ch-rtt"|"ch-save-data"|"ch-ua"|"ch-ua-arch"|"ch-ua-bitness"|"ch-ua-platform"|"ch-ua-model"|"ch-ua-mobile"|"ch-ua-form-factors"|"ch-ua-full-version"|"ch-ua-full-version-list"|"ch-ua-platform-version"|"ch-ua-wow64"|"ch-viewport-height"|"ch-viewport-width"|"ch-width"|"clipboard-read"|"clipboard-write"|"compute-pressure"|"cross-origin-isolated"|"deferred-fetch"|"digital-credentials-get"|"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"|"identity-credentials-get"|"idle-detection"|"interest-cohort"|"join-ad-interest-group"|"keyboard-map"|"local-fonts"|"magnetometer"|"microphone"|"midi"|"otp-credentials"|"payment"|"picture-in-picture"|"private-aggregation"|"private-state-token-issuance"|"private-state-token-redemption"|"publickey-credentials-create"|"publickey-credentials-get"|"run-ad-auction"|"screen-wake-lock"|"serial"|"shared-autofill"|"shared-storage"|"shared-storage-select-url"|"smart-card"|"speaker-selection"|"storage-access"|"sub-apps"|"sync-xhr"|"unload"|"usb"|"usb-unrestricted"|"vertical-scroll"|"web-printing"|"web-share"|"window-management"|"xr-spatial-tracking";
/** /**
* Reason for a permissions policy feature to be disabled. * Reason for a permissions policy feature to be disabled.
*/ */
@ -11866,7 +11957,7 @@ https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-expl
/** /**
* 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"|"CookieDisabled"|"HTTPAuthRequired"|"CookieFlushed"|"BroadcastChannelOnMessage"|"WebViewSettingsChanged"|"WebViewJavaScriptObjectChanged"|"WebViewMessageListenerInjected"|"WebViewSafeBrowsingAllowlistChanged"|"WebViewDocumentStartJavascriptChanged"|"WebSocket"|"WebTransport"|"WebRTC"|"MainResourceHasCacheControlNoStore"|"MainResourceHasCacheControlNoCache"|"SubresourceHasCacheControlNoStore"|"SubresourceHasCacheControlNoCache"|"ContainsPlugins"|"DocumentLoaded"|"OutstandingNetworkRequestOthers"|"RequestedMIDIPermission"|"RequestedAudioCapturePermission"|"RequestedVideoCapturePermission"|"RequestedBackForwardCacheBlockedSensors"|"RequestedBackgroundWorkPermission"|"BroadcastChannel"|"WebXR"|"SharedWorker"|"WebLocks"|"WebHID"|"WebShare"|"RequestedStorageAccessGrant"|"WebNfc"|"OutstandingNetworkRequestFetch"|"OutstandingNetworkRequestXHR"|"AppBanner"|"Printing"|"WebDatabase"|"PictureInPicture"|"Portal"|"SpeechRecognizer"|"IdleManager"|"PaymentManager"|"SpeechSynthesis"|"KeyboardLock"|"WebOTPService"|"OutstandingNetworkRequestDirectSocket"|"InjectedJavascript"|"InjectedStyleSheet"|"KeepaliveRequest"|"IndexedDBEvent"|"Dummy"|"JsNetworkRequestReceivedCacheControlNoStoreResource"|"WebRTCSticky"|"WebTransportSticky"|"WebSocketSticky"|"SmartCard"|"LiveMediaStreamTrack"|"UnloadHandler"|"ParserAborted"|"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"|"RequestedByWebViewClient"; 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"|"CookieDisabled"|"HTTPAuthRequired"|"CookieFlushed"|"BroadcastChannelOnMessage"|"WebViewSettingsChanged"|"WebViewJavaScriptObjectChanged"|"WebViewMessageListenerInjected"|"WebViewSafeBrowsingAllowlistChanged"|"WebViewDocumentStartJavascriptChanged"|"WebSocket"|"WebTransport"|"WebRTC"|"MainResourceHasCacheControlNoStore"|"MainResourceHasCacheControlNoCache"|"SubresourceHasCacheControlNoStore"|"SubresourceHasCacheControlNoCache"|"ContainsPlugins"|"DocumentLoaded"|"OutstandingNetworkRequestOthers"|"RequestedMIDIPermission"|"RequestedAudioCapturePermission"|"RequestedVideoCapturePermission"|"RequestedBackForwardCacheBlockedSensors"|"RequestedBackgroundWorkPermission"|"BroadcastChannel"|"WebXR"|"SharedWorker"|"WebLocks"|"WebHID"|"WebShare"|"RequestedStorageAccessGrant"|"WebNfc"|"OutstandingNetworkRequestFetch"|"OutstandingNetworkRequestXHR"|"AppBanner"|"Printing"|"WebDatabase"|"PictureInPicture"|"SpeechRecognizer"|"IdleManager"|"PaymentManager"|"SpeechSynthesis"|"KeyboardLock"|"WebOTPService"|"OutstandingNetworkRequestDirectSocket"|"InjectedJavascript"|"InjectedStyleSheet"|"KeepaliveRequest"|"IndexedDBEvent"|"Dummy"|"JsNetworkRequestReceivedCacheControlNoStoreResource"|"WebRTCSticky"|"WebTransportSticky"|"WebSocketSticky"|"SmartCard"|"LiveMediaStreamTrack"|"UnloadHandler"|"ParserAborted"|"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"|"RequestedByWebViewClient";
/** /**
* Types of not restored reasons for back-forward cache. * Types of not restored reasons for back-forward cache.
*/ */
@ -14053,6 +14144,25 @@ int
eventReportWindows: AttributionReportingEventReportWindows; eventReportWindows: AttributionReportingEventReportWindows;
} }
export type AttributionReportingTriggerDataMatching = "exact"|"modulus"; export type AttributionReportingTriggerDataMatching = "exact"|"modulus";
export interface AttributionReportingAggregatableDebugReportingData {
keyPiece: UnsignedInt128AsBase16;
/**
* number instead of integer because not all uint32 can be represented by
int
*/
value: number;
types: string[];
}
export interface AttributionReportingAggregatableDebugReportingConfig {
/**
* number instead of integer because not all uint32 can be represented by
int, only present for source registrations
*/
budget?: number;
keyPiece: UnsignedInt128AsBase16;
debugData: AttributionReportingAggregatableDebugReportingData[];
aggregationCoordinatorOrigin?: string;
}
export interface AttributionReportingSourceRegistration { export interface AttributionReportingSourceRegistration {
time: Network.TimeSinceEpoch; time: Network.TimeSinceEpoch;
/** /**
@ -14074,8 +14184,10 @@ int
aggregationKeys: AttributionReportingAggregationKeysEntry[]; aggregationKeys: AttributionReportingAggregationKeysEntry[];
debugKey?: UnsignedInt64AsBase10; debugKey?: UnsignedInt64AsBase10;
triggerDataMatching: AttributionReportingTriggerDataMatching; triggerDataMatching: AttributionReportingTriggerDataMatching;
destinationLimitPriority: SignedInt64AsBase10;
aggregatableDebugReportingConfig: AttributionReportingAggregatableDebugReportingConfig;
} }
export type AttributionReportingSourceRegistrationResult = "success"|"internalError"|"insufficientSourceCapacity"|"insufficientUniqueDestinationCapacity"|"excessiveReportingOrigins"|"prohibitedByBrowserPolicy"|"successNoised"|"destinationReportingLimitReached"|"destinationGlobalLimitReached"|"destinationBothLimitsReached"|"reportingOriginsPerSiteLimitReached"|"exceedsMaxChannelCapacity"|"exceedsMaxTriggerStateCardinality"; export type AttributionReportingSourceRegistrationResult = "success"|"internalError"|"insufficientSourceCapacity"|"insufficientUniqueDestinationCapacity"|"excessiveReportingOrigins"|"prohibitedByBrowserPolicy"|"successNoised"|"destinationReportingLimitReached"|"destinationGlobalLimitReached"|"destinationBothLimitsReached"|"reportingOriginsPerSiteLimitReached"|"exceedsMaxChannelCapacity"|"exceedsMaxTriggerStateCardinality"|"destinationPerDayReportingLimitReached";
export type AttributionReportingSourceRegistrationTimeConfig = "include"|"exclude"; export type AttributionReportingSourceRegistrationTimeConfig = "include"|"exclude";
export interface AttributionReportingAggregatableValueDictEntry { export interface AttributionReportingAggregatableValueDictEntry {
key: string; key: string;
@ -14084,6 +14196,7 @@ int
int int
*/ */
value: number; value: number;
filteringId: UnsignedInt64AsBase10;
} }
export interface AttributionReportingAggregatableValueEntry { export interface AttributionReportingAggregatableValueEntry {
values: AttributionReportingAggregatableValueDictEntry[]; values: AttributionReportingAggregatableValueDictEntry[];
@ -14111,10 +14224,12 @@ int
eventTriggerData: AttributionReportingEventTriggerData[]; eventTriggerData: AttributionReportingEventTriggerData[];
aggregatableTriggerData: AttributionReportingAggregatableTriggerData[]; aggregatableTriggerData: AttributionReportingAggregatableTriggerData[];
aggregatableValues: AttributionReportingAggregatableValueEntry[]; aggregatableValues: AttributionReportingAggregatableValueEntry[];
aggregatableFilteringIdMaxBytes: number;
debugReporting: boolean; debugReporting: boolean;
aggregationCoordinatorOrigin?: string; aggregationCoordinatorOrigin?: string;
sourceRegistrationTimeConfig: AttributionReportingSourceRegistrationTimeConfig; sourceRegistrationTimeConfig: AttributionReportingSourceRegistrationTimeConfig;
triggerContextId?: string; triggerContextId?: string;
aggregatableDebugReportingConfig: AttributionReportingAggregatableDebugReportingConfig;
} }
export type AttributionReportingEventLevelResult = "success"|"successDroppedLowerPriority"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"deduplicated"|"excessiveAttributions"|"priorityTooLow"|"neverAttributedSource"|"excessiveReportingOrigins"|"noMatchingSourceFilterData"|"prohibitedByBrowserPolicy"|"noMatchingConfigurations"|"excessiveReports"|"falselyAttributedSource"|"reportWindowPassed"|"notRegistered"|"reportWindowNotStarted"|"noMatchingTriggerData"; export type AttributionReportingEventLevelResult = "success"|"successDroppedLowerPriority"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"deduplicated"|"excessiveAttributions"|"priorityTooLow"|"neverAttributedSource"|"excessiveReportingOrigins"|"noMatchingSourceFilterData"|"prohibitedByBrowserPolicy"|"noMatchingConfigurations"|"excessiveReports"|"falselyAttributedSource"|"reportWindowPassed"|"notRegistered"|"reportWindowNotStarted"|"noMatchingTriggerData";
export type AttributionReportingAggregatableResult = "success"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"excessiveAttributions"|"excessiveReportingOrigins"|"noHistograms"|"insufficientBudget"|"noMatchingSourceFilterData"|"notRegistered"|"prohibitedByBrowserPolicy"|"deduplicated"|"reportWindowPassed"|"excessiveReports"; export type AttributionReportingAggregatableResult = "success"|"internalError"|"noCapacityForAttributionDestination"|"noMatchingSources"|"excessiveAttributions"|"excessiveReportingOrigins"|"noHistograms"|"insufficientBudget"|"noMatchingSourceFilterData"|"notRegistered"|"prohibitedByBrowserPolicy"|"deduplicated"|"reportWindowPassed"|"excessiveReports";
@ -14980,7 +15095,7 @@ supported.
browserContextId?: Browser.BrowserContextID; browserContextId?: Browser.BrowserContextID;
/** /**
* Provides additional details for specific target types. For example, for * Provides additional details for specific target types. For example, for
the type of "page", this may be set to "portal" or "prerender". the type of "page", this may be set to "prerender".
*/ */
subtype?: string; subtype?: string;
} }
@ -16807,7 +16922,7 @@ possible for multiple rule sets and links to trigger a single attempt.
/** /**
* List of FinalStatus reasons for Prerender2. * List of FinalStatus reasons for Prerender2.
*/ */
export type PrerenderFinalStatus = "Activated"|"Destroyed"|"LowEndDevice"|"InvalidSchemeRedirect"|"InvalidSchemeNavigation"|"NavigationRequestBlockedByCsp"|"MainFrameNavigation"|"MojoBinderPolicy"|"RendererProcessCrashed"|"RendererProcessKilled"|"Download"|"TriggerDestroyed"|"NavigationNotCommitted"|"NavigationBadHttpStatus"|"ClientCertRequested"|"NavigationRequestNetworkError"|"CancelAllHostsForTesting"|"DidFailLoad"|"Stop"|"SslCertificateError"|"LoginAuthRequested"|"UaChangeRequiresReload"|"BlockedByClient"|"AudioOutputDeviceRequested"|"MixedContent"|"TriggerBackgrounded"|"MemoryLimitExceeded"|"DataSaverEnabled"|"TriggerUrlHasEffectiveUrl"|"ActivatedBeforeStarted"|"InactivePageRestriction"|"StartFailed"|"TimeoutBackgrounded"|"CrossSiteRedirectInInitialNavigation"|"CrossSiteNavigationInInitialNavigation"|"SameSiteCrossOriginRedirectNotOptInInInitialNavigation"|"SameSiteCrossOriginNavigationNotOptInInInitialNavigation"|"ActivationNavigationParameterMismatch"|"ActivatedInBackground"|"EmbedderHostDisallowed"|"ActivationNavigationDestroyedBeforeSuccess"|"TabClosedByUserGesture"|"TabClosedWithoutUserGesture"|"PrimaryMainFrameRendererProcessCrashed"|"PrimaryMainFrameRendererProcessKilled"|"ActivationFramePolicyNotCompatible"|"PreloadingDisabled"|"BatterySaverEnabled"|"ActivatedDuringMainFrameNavigation"|"PreloadingUnsupportedByWebContents"|"CrossSiteRedirectInMainFrameNavigation"|"CrossSiteNavigationInMainFrameNavigation"|"SameSiteCrossOriginRedirectNotOptInInMainFrameNavigation"|"SameSiteCrossOriginNavigationNotOptInInMainFrameNavigation"|"MemoryPressureOnTrigger"|"MemoryPressureAfterTriggered"|"PrerenderingDisabledByDevTools"|"SpeculationRuleRemoved"|"ActivatedWithAuxiliaryBrowsingContexts"|"MaxNumOfRunningEagerPrerendersExceeded"|"MaxNumOfRunningNonEagerPrerendersExceeded"|"MaxNumOfRunningEmbedderPrerendersExceeded"|"PrerenderingUrlHasEffectiveUrl"|"RedirectedPrerenderingUrlHasEffectiveUrl"|"ActivationUrlHasEffectiveUrl"|"JavaScriptInterfaceAdded"|"JavaScriptInterfaceRemoved"|"AllPrerenderingCanceled"; export type PrerenderFinalStatus = "Activated"|"Destroyed"|"LowEndDevice"|"InvalidSchemeRedirect"|"InvalidSchemeNavigation"|"NavigationRequestBlockedByCsp"|"MainFrameNavigation"|"MojoBinderPolicy"|"RendererProcessCrashed"|"RendererProcessKilled"|"Download"|"TriggerDestroyed"|"NavigationNotCommitted"|"NavigationBadHttpStatus"|"ClientCertRequested"|"NavigationRequestNetworkError"|"CancelAllHostsForTesting"|"DidFailLoad"|"Stop"|"SslCertificateError"|"LoginAuthRequested"|"UaChangeRequiresReload"|"BlockedByClient"|"AudioOutputDeviceRequested"|"MixedContent"|"TriggerBackgrounded"|"MemoryLimitExceeded"|"DataSaverEnabled"|"TriggerUrlHasEffectiveUrl"|"ActivatedBeforeStarted"|"InactivePageRestriction"|"StartFailed"|"TimeoutBackgrounded"|"CrossSiteRedirectInInitialNavigation"|"CrossSiteNavigationInInitialNavigation"|"SameSiteCrossOriginRedirectNotOptInInInitialNavigation"|"SameSiteCrossOriginNavigationNotOptInInInitialNavigation"|"ActivationNavigationParameterMismatch"|"ActivatedInBackground"|"EmbedderHostDisallowed"|"ActivationNavigationDestroyedBeforeSuccess"|"TabClosedByUserGesture"|"TabClosedWithoutUserGesture"|"PrimaryMainFrameRendererProcessCrashed"|"PrimaryMainFrameRendererProcessKilled"|"ActivationFramePolicyNotCompatible"|"PreloadingDisabled"|"BatterySaverEnabled"|"ActivatedDuringMainFrameNavigation"|"PreloadingUnsupportedByWebContents"|"CrossSiteRedirectInMainFrameNavigation"|"CrossSiteNavigationInMainFrameNavigation"|"SameSiteCrossOriginRedirectNotOptInInMainFrameNavigation"|"SameSiteCrossOriginNavigationNotOptInInMainFrameNavigation"|"MemoryPressureOnTrigger"|"MemoryPressureAfterTriggered"|"PrerenderingDisabledByDevTools"|"SpeculationRuleRemoved"|"ActivatedWithAuxiliaryBrowsingContexts"|"MaxNumOfRunningEagerPrerendersExceeded"|"MaxNumOfRunningNonEagerPrerendersExceeded"|"MaxNumOfRunningEmbedderPrerendersExceeded"|"PrerenderingUrlHasEffectiveUrl"|"RedirectedPrerenderingUrlHasEffectiveUrl"|"ActivationUrlHasEffectiveUrl"|"JavaScriptInterfaceAdded"|"JavaScriptInterfaceRemoved"|"AllPrerenderingCanceled"|"WindowClosed";
/** /**
* Preloading status values, see also PreloadingTriggeringOutcome. This * Preloading status values, see also PreloadingTriggeringOutcome. This
status is shared by prefetchStatusUpdated and prerenderStatusUpdated. status is shared by prefetchStatusUpdated and prerenderStatusUpdated.
@ -17021,6 +17136,10 @@ https://www.iana.org/assignments/media-types/media-types.xhtml
accepts: FileHandlerAccept[]; accepts: FileHandlerAccept[];
displayName: string; displayName: string;
} }
/**
* If user prefers opening the app in browser or an app window.
*/
export type DisplayMode = "standalone"|"browser";
/** /**
@ -17061,7 +17180,7 @@ manifestId.
export type installReturnValue = { export type installReturnValue = {
} }
/** /**
* Uninstals the given manifest_id and closes any opened app windows. * Uninstalls the given manifest_id and closes any opened app windows.
*/ */
export type uninstallParameters = { export type uninstallParameters = {
manifestId: string; manifestId: string;
@ -17090,7 +17209,7 @@ the files. The API returns one or more page Target.TargetIDs which can be
used to attach to via Target.attachToTarget or similar APIs. used to attach to via Target.attachToTarget or similar APIs.
If some files in the parameters cannot be handled by the web app, they will If some files in the parameters cannot be handled by the web app, they will
be ignored. If none of the files can be handled, this API returns an error. be ignored. If none of the files can be handled, this API returns an error.
If no files provided as the parameter, this API also returns an error. If no files are provided as the parameter, this API also returns an error.
According to the definition of the file handlers in the manifest file, one According to the definition of the file handlers in the manifest file, one
Target.TargetID may represent a page handling one or more files. The order Target.TargetID may represent a page handling one or more files. The order
@ -17111,13 +17230,44 @@ TODO(crbug.com/339454034): Check the existences of the input files.
/** /**
* Opens the current page in its web app identified by the manifest id, needs * Opens the current page in its web app identified by the manifest id, needs
to be called on a page target. This function returns immediately without to be called on a page target. This function returns immediately without
waiting for the app finishing loading. waiting for the app to finish loading.
*/ */
export type openCurrentPageInAppParameters = { export type openCurrentPageInAppParameters = {
manifestId: string; manifestId: string;
} }
export type openCurrentPageInAppReturnValue = { export type openCurrentPageInAppReturnValue = {
} }
/**
* Changes user settings of the web app identified by its manifestId. If the
app was not installed, this command returns an error. Unset parameters will
be ignored; unrecognized values will cause an error.
Unlike the ones defined in the manifest files of the web apps, these
settings are provided by the browser and controlled by the users, they
impact the way the browser handling the web apps.
See the comment of each parameter.
*/
export type changeAppUserSettingsParameters = {
manifestId: string;
/**
* If user allows the links clicked on by the user in the app's scope, or
extended scope if the manifest has scope extensions and the flags
`DesktopPWAsLinkCapturingWithScopeExtensions` and
`WebAppEnableScopeExtensions` are enabled.
Note, the API does not support resetting the linkCapturing to the
initial value, uninstalling and installing the web app again will reset
it.
TODO(crbug.com/339453269): Setting this value on ChromeOS is not
supported yet.
*/
linkCapturing?: boolean;
displayMode?: DisplayMode;
}
export type changeAppUserSettingsReturnValue = {
}
} }
/** /**
@ -19824,6 +19974,7 @@ Error was thrown.
"Network.responseReceivedExtraInfo": Network.responseReceivedExtraInfoPayload; "Network.responseReceivedExtraInfo": Network.responseReceivedExtraInfoPayload;
"Network.responseReceivedEarlyHints": Network.responseReceivedEarlyHintsPayload; "Network.responseReceivedEarlyHints": Network.responseReceivedEarlyHintsPayload;
"Network.trustTokenOperationDone": Network.trustTokenOperationDonePayload; "Network.trustTokenOperationDone": Network.trustTokenOperationDonePayload;
"Network.policyUpdated": Network.policyUpdatedPayload;
"Network.subresourceWebBundleMetadataReceived": Network.subresourceWebBundleMetadataReceivedPayload; "Network.subresourceWebBundleMetadataReceived": Network.subresourceWebBundleMetadataReceivedPayload;
"Network.subresourceWebBundleMetadataError": Network.subresourceWebBundleMetadataErrorPayload; "Network.subresourceWebBundleMetadataError": Network.subresourceWebBundleMetadataErrorPayload;
"Network.subresourceWebBundleInnerResponseParsed": Network.subresourceWebBundleInnerResponseParsedPayload; "Network.subresourceWebBundleInnerResponseParsed": Network.subresourceWebBundleInnerResponseParsedPayload;
@ -20139,6 +20290,8 @@ Error was thrown.
"Emulation.getOverriddenSensorInformation": Emulation.getOverriddenSensorInformationParameters; "Emulation.getOverriddenSensorInformation": Emulation.getOverriddenSensorInformationParameters;
"Emulation.setSensorOverrideEnabled": Emulation.setSensorOverrideEnabledParameters; "Emulation.setSensorOverrideEnabled": Emulation.setSensorOverrideEnabledParameters;
"Emulation.setSensorOverrideReadings": Emulation.setSensorOverrideReadingsParameters; "Emulation.setSensorOverrideReadings": Emulation.setSensorOverrideReadingsParameters;
"Emulation.setPressureSourceOverrideEnabled": Emulation.setPressureSourceOverrideEnabledParameters;
"Emulation.setPressureStateOverride": Emulation.setPressureStateOverrideParameters;
"Emulation.setIdleOverride": Emulation.setIdleOverrideParameters; "Emulation.setIdleOverride": Emulation.setIdleOverrideParameters;
"Emulation.clearIdleOverride": Emulation.clearIdleOverrideParameters; "Emulation.clearIdleOverride": Emulation.clearIdleOverrideParameters;
"Emulation.setNavigatorOverrides": Emulation.setNavigatorOverridesParameters; "Emulation.setNavigatorOverrides": Emulation.setNavigatorOverridesParameters;
@ -20159,6 +20312,7 @@ Error was thrown.
"IO.close": IO.closeParameters; "IO.close": IO.closeParameters;
"IO.read": IO.readParameters; "IO.read": IO.readParameters;
"IO.resolveBlob": IO.resolveBlobParameters; "IO.resolveBlob": IO.resolveBlobParameters;
"FileSystem.getDirectory": FileSystem.getDirectoryParameters;
"IndexedDB.clearObjectStore": IndexedDB.clearObjectStoreParameters; "IndexedDB.clearObjectStore": IndexedDB.clearObjectStoreParameters;
"IndexedDB.deleteDatabase": IndexedDB.deleteDatabaseParameters; "IndexedDB.deleteDatabase": IndexedDB.deleteDatabaseParameters;
"IndexedDB.deleteObjectStoreEntries": IndexedDB.deleteObjectStoreEntriesParameters; "IndexedDB.deleteObjectStoreEntries": IndexedDB.deleteObjectStoreEntriesParameters;
@ -20461,6 +20615,7 @@ Error was thrown.
"PWA.launch": PWA.launchParameters; "PWA.launch": PWA.launchParameters;
"PWA.launchFilesInApp": PWA.launchFilesInAppParameters; "PWA.launchFilesInApp": PWA.launchFilesInAppParameters;
"PWA.openCurrentPageInApp": PWA.openCurrentPageInAppParameters; "PWA.openCurrentPageInApp": PWA.openCurrentPageInAppParameters;
"PWA.changeAppUserSettings": PWA.changeAppUserSettingsParameters;
"Console.clearMessages": Console.clearMessagesParameters; "Console.clearMessages": Console.clearMessagesParameters;
"Console.disable": Console.disableParameters; "Console.disable": Console.disableParameters;
"Console.enable": Console.enableParameters; "Console.enable": Console.enableParameters;
@ -20735,6 +20890,8 @@ Error was thrown.
"Emulation.getOverriddenSensorInformation": Emulation.getOverriddenSensorInformationReturnValue; "Emulation.getOverriddenSensorInformation": Emulation.getOverriddenSensorInformationReturnValue;
"Emulation.setSensorOverrideEnabled": Emulation.setSensorOverrideEnabledReturnValue; "Emulation.setSensorOverrideEnabled": Emulation.setSensorOverrideEnabledReturnValue;
"Emulation.setSensorOverrideReadings": Emulation.setSensorOverrideReadingsReturnValue; "Emulation.setSensorOverrideReadings": Emulation.setSensorOverrideReadingsReturnValue;
"Emulation.setPressureSourceOverrideEnabled": Emulation.setPressureSourceOverrideEnabledReturnValue;
"Emulation.setPressureStateOverride": Emulation.setPressureStateOverrideReturnValue;
"Emulation.setIdleOverride": Emulation.setIdleOverrideReturnValue; "Emulation.setIdleOverride": Emulation.setIdleOverrideReturnValue;
"Emulation.clearIdleOverride": Emulation.clearIdleOverrideReturnValue; "Emulation.clearIdleOverride": Emulation.clearIdleOverrideReturnValue;
"Emulation.setNavigatorOverrides": Emulation.setNavigatorOverridesReturnValue; "Emulation.setNavigatorOverrides": Emulation.setNavigatorOverridesReturnValue;
@ -20755,6 +20912,7 @@ Error was thrown.
"IO.close": IO.closeReturnValue; "IO.close": IO.closeReturnValue;
"IO.read": IO.readReturnValue; "IO.read": IO.readReturnValue;
"IO.resolveBlob": IO.resolveBlobReturnValue; "IO.resolveBlob": IO.resolveBlobReturnValue;
"FileSystem.getDirectory": FileSystem.getDirectoryReturnValue;
"IndexedDB.clearObjectStore": IndexedDB.clearObjectStoreReturnValue; "IndexedDB.clearObjectStore": IndexedDB.clearObjectStoreReturnValue;
"IndexedDB.deleteDatabase": IndexedDB.deleteDatabaseReturnValue; "IndexedDB.deleteDatabase": IndexedDB.deleteDatabaseReturnValue;
"IndexedDB.deleteObjectStoreEntries": IndexedDB.deleteObjectStoreEntriesReturnValue; "IndexedDB.deleteObjectStoreEntries": IndexedDB.deleteObjectStoreEntriesReturnValue;
@ -21057,6 +21215,7 @@ Error was thrown.
"PWA.launch": PWA.launchReturnValue; "PWA.launch": PWA.launchReturnValue;
"PWA.launchFilesInApp": PWA.launchFilesInAppReturnValue; "PWA.launchFilesInApp": PWA.launchFilesInAppReturnValue;
"PWA.openCurrentPageInApp": PWA.openCurrentPageInAppReturnValue; "PWA.openCurrentPageInApp": PWA.openCurrentPageInAppReturnValue;
"PWA.changeAppUserSettings": PWA.changeAppUserSettingsReturnValue;
"Console.clearMessages": Console.clearMessagesReturnValue; "Console.clearMessages": Console.clearMessagesReturnValue;
"Console.disable": Console.disableReturnValue; "Console.disable": Console.disableReturnValue;
"Console.enable": Console.enableReturnValue; "Console.enable": Console.enableReturnValue;

View file

@ -13172,8 +13172,8 @@ export interface BrowserType<Unused = {}> {
* *
* An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
* single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
* certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that
* provided with a glob pattern to match the origins that the certificate is valid for. * the certificate is valid for.
* *
* **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported.
* *
@ -13182,7 +13182,7 @@ export interface BrowserType<Unused = {}> {
*/ */
clientCertificates?: Array<{ clientCertificates?: Array<{
/** /**
* Glob pattern to match against the request origin that the certificate is valid for. * Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port.
*/ */
origin: string; origin: string;
@ -15583,8 +15583,8 @@ export interface APIRequest {
* *
* An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
* single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
* certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that
* provided with a glob pattern to match the origins that the certificate is valid for. * the certificate is valid for.
* *
* **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported.
* *
@ -15593,7 +15593,7 @@ export interface APIRequest {
*/ */
clientCertificates?: Array<{ clientCertificates?: Array<{
/** /**
* Glob pattern to match against the request origin that the certificate is valid for. * Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port.
*/ */
origin: string; origin: string;
@ -16776,8 +16776,8 @@ export interface Browser extends EventEmitter {
* *
* An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
* single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
* certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that
* provided with a glob pattern to match the origins that the certificate is valid for. * the certificate is valid for.
* *
* **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported.
* *
@ -16786,7 +16786,7 @@ export interface Browser extends EventEmitter {
*/ */
clientCertificates?: Array<{ clientCertificates?: Array<{
/** /**
* Glob pattern to match against the request origin that the certificate is valid for. * Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port.
*/ */
origin: string; origin: string;
@ -20226,8 +20226,8 @@ export interface BrowserContextOptions {
* *
* An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
* single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
* certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that
* provided with a glob pattern to match the origins that the certificate is valid for. * the certificate is valid for.
* *
* **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported.
* *
@ -20236,7 +20236,7 @@ export interface BrowserContextOptions {
*/ */
clientCertificates?: Array<{ clientCertificates?: Array<{
/** /**
* Glob pattern to match against the request origin that the certificate is valid for. * Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port.
*/ */
origin: string; origin: string;

View file

@ -355,7 +355,7 @@ const testOptions: [string, string][] = [
['--max-failures <N>', `Stop after the first N failures`], ['--max-failures <N>', `Stop after the first N failures`],
['--no-deps', 'Do not run project dependencies'], ['--no-deps', 'Do not run project dependencies'],
['--output <dir>', `Folder for output artifacts (default: "test-results")`], ['--output <dir>', `Folder for output artifacts (default: "test-results")`],
['--only-changed [ref]', `Only run tests that have been changed between 'HEAD' and 'ref'. Defaults to running all uncommitted changes. Only supports Git.`], ['--only-changed [ref]', `Only run test files that have been changed between 'HEAD' and 'ref'. Defaults to running all uncommitted changes. Only supports Git.`],
['--pass-with-no-tests', `Makes test run succeed even if no tests were found`], ['--pass-with-no-tests', `Makes test run succeed even if no tests were found`],
['--project <project-name...>', `Only run tests from the specified list of projects, supports '*' wildcard (default: run all projects)`], ['--project <project-name...>', `Only run tests from the specified list of projects, supports '*' wildcard (default: run all projects)`],
['--quiet', `Suppress stdio`], ['--quiet', `Suppress stdio`],

View file

@ -31,7 +31,7 @@ import type { Matcher } from '../util';
import { Suite } from '../common/test'; import { Suite } from '../common/test';
import { buildDependentProjects, buildTeardownToSetupsMap, filterProjects } from './projectUtils'; import { buildDependentProjects, buildTeardownToSetupsMap, filterProjects } from './projectUtils';
import { FailureTracker } from './failureTracker'; import { FailureTracker } from './failureTracker';
import { detectChangedTests } from './vcs'; import { detectChangedTestFiles } from './vcs';
const readDirAsync = promisify(fs.readdir); const readDirAsync = promisify(fs.readdir);
@ -232,7 +232,7 @@ function createLoadTask(mode: 'out-of-process' | 'in-process', options: { filter
if (testRun.config.cliOnlyChanged && options.filterOnlyChanged) { if (testRun.config.cliOnlyChanged && options.filterOnlyChanged) {
for (const plugin of testRun.config.plugins) for (const plugin of testRun.config.plugins)
await plugin.instance?.populateDependencies?.(); await plugin.instance?.populateDependencies?.();
const changedFiles = await detectChangedTests(testRun.config.cliOnlyChanged, testRun.config.configDir); const changedFiles = await detectChangedTestFiles(testRun.config.cliOnlyChanged, testRun.config.configDir);
cliOnlyChangedMatcher = file => changedFiles.has(file); cliOnlyChangedMatcher = file => changedFiles.has(file);
} }

View file

@ -18,7 +18,7 @@ import childProcess from 'child_process';
import { affectedTestFiles } from '../transform/compilationCache'; import { affectedTestFiles } from '../transform/compilationCache';
import path from 'path'; import path from 'path';
export async function detectChangedTests(baseCommit: string, configDir: string): Promise<Set<string>> { export async function detectChangedTestFiles(baseCommit: string, configDir: string): Promise<Set<string>> {
function gitFileList(command: string) { function gitFileList(command: string) {
try { try {
return childProcess.execSync( return childProcess.execSync(

View file

@ -5208,8 +5208,8 @@ export interface PlaywrightTestOptions {
* *
* An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
* single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
* certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that
* provided with a glob pattern to match the origins that the certificate is valid for. * the certificate is valid for.
* *
* **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported.
* *

View file

@ -107,8 +107,8 @@ export const renderAction = (
{(showDuration || showBadges) && <div className='spacer'></div>} {(showDuration || showBadges) && <div className='spacer'></div>}
{showDuration && <div className='action-duration'>{time || <span className='codicon codicon-loading'></span>}</div>} {showDuration && <div className='action-duration'>{time || <span className='codicon codicon-loading'></span>}</div>}
{showBadges && <div className='action-icons' onClick={() => revealConsole?.()}> {showBadges && <div className='action-icons' onClick={() => revealConsole?.()}>
{!!errors && <div className='action-icon'><span className='codicon codicon-error'></span><span className="action-icon-value">{errors}</span></div>} {!!errors && <div className='action-icon'><span className='codicon codicon-error'></span><span className='action-icon-value'>{errors}</span></div>}
{!!warnings && <div className='action-icon'><span className='codicon codicon-warning'></span><span className="action-icon-value">{warnings}</span></div>} {!!warnings && <div className='action-icon'><span className='codicon codicon-warning'></span><span className='action-icon-value'>{warnings}</span></div>}
</div>} </div>}
</>; </>;
}; };

View file

@ -73,6 +73,14 @@
overflow: hidden; overflow: hidden;
} }
a.call-value {
text-decoration: none;
}
a.call-value:hover {
text-decoration: underline;
}
.call-value::before { .call-value::before {
content: '\00a0'; content: '\00a0';
} }

View file

@ -33,6 +33,12 @@ export const MetadataView: React.FunctionComponent<{
{model.channel && <div className='call-line'>channel:<span className='call-value string' title={model.channel}>{model.channel}</span></div>} {model.channel && <div className='call-line'>channel:<span className='call-value string' title={model.channel}>{model.channel}</span></div>}
{model.platform && <div className='call-line'>platform:<span className='call-value string' title={model.platform}>{model.platform}</span></div>} {model.platform && <div className='call-line'>platform:<span className='call-value string' title={model.platform}>{model.platform}</span></div>}
{model.options.userAgent && <div className='call-line'>user agent:<span className='call-value datetime' title={model.options.userAgent}>{model.options.userAgent}</span></div>} {model.options.userAgent && <div className='call-line'>user agent:<span className='call-value datetime' title={model.options.userAgent}>{model.options.userAgent}</span></div>}
{model.options.baseURL && (
<>
<div className='call-section' style={{ paddingTop: 2 }}>Config</div>
<div className='call-line'>baseURL:<a className='call-value string' href={model.options.baseURL} title={model.options.baseURL} target='_blank' rel='noopener noreferrer'>{model.options.baseURL}</a></div>
</>
)}
<div className='call-section'>Viewport</div> <div className='call-section'>Viewport</div>
{model.options.viewport && <div className='call-line'>width:<span className='call-value number' title={String(!!model.options.viewport?.width)}>{model.options.viewport.width}</span></div>} {model.options.viewport && <div className='call-line'>width:<span className='call-value number' title={String(!!model.options.viewport?.width)}>{model.options.viewport.width}</span></div>}
{model.options.viewport && <div className='call-line'>height:<span className='call-value number' title={String(!!model.options.viewport?.height)}>{model.options.viewport.height}</span></div>} {model.options.viewport && <div className='call-line'>height:<span className='call-value number' title={String(!!model.options.viewport?.height)}>{model.options.viewport.height}</span></div>}

View file

@ -16,13 +16,22 @@
.settings-view { .settings-view {
flex: none; flex: none;
margin-top: 4px;
} }
.settings-view .setting label { .settings-view .setting label {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
margin: 6px 2px; margin: 4px 2px;
}
.settings-view .setting:first-of-type label {
margin-top: 2px;
}
.settings-view .setting:last-of-type label {
margin-bottom: 2px;
} }
.settings-view .setting input { .settings-view .setting input {

View file

@ -22,13 +22,11 @@ export const SettingsView: React.FunctionComponent<{
settings: Setting<boolean>[], settings: Setting<boolean>[],
}> = ({ settings }) => { }> = ({ settings }) => {
return <div className='vbox settings-view'> return <div className='vbox settings-view'>
{settings.map(setting => { {settings.map(([value, set, title]) => {
return <div key={setting.name} className='setting'> return <div key={title} className='setting'>
<label> <label>
<input type='checkbox' checked={setting.value} onClick={() => { <input type='checkbox' checked={value} onClick={() => set(!value)}/>
setting.set(!setting.value); {title}
}}/>
{setting.title}
</label> </label>
</div>; </div>;
})} })}

View file

@ -25,11 +25,13 @@ import type { ContextEntry } from '../entries';
import type { SourceLocation } from './modelUtil'; import type { SourceLocation } from './modelUtil';
import { idForAction, MultiTraceModel } from './modelUtil'; import { idForAction, MultiTraceModel } from './modelUtil';
import { Workbench } from './workbench'; import { Workbench } from './workbench';
import { type Setting } from '@web/uiUtils';
export const TraceView: React.FC<{ export const TraceView: React.FC<{
showRouteActionsSetting: Setting<boolean>,
item: { treeItem?: TreeItem, testFile?: SourceLocation, testCase?: reporterTypes.TestCase }, item: { treeItem?: TreeItem, testFile?: SourceLocation, testCase?: reporterTypes.TestCase },
rootDir?: string, rootDir?: string,
}> = ({ item, rootDir }) => { }> = ({ showRouteActionsSetting, item, rootDir }) => {
const [model, setModel] = React.useState<{ model: MultiTraceModel, isLive: boolean } | undefined>(); const [model, setModel] = React.useState<{ model: MultiTraceModel, isLive: boolean } | undefined>();
const [counter, setCounter] = React.useState(0); const [counter, setCounter] = React.useState(0);
const pollTimer = React.useRef<NodeJS.Timeout | null>(null); const pollTimer = React.useRef<NodeJS.Timeout | null>(null);
@ -87,6 +89,7 @@ export const TraceView: React.FC<{
return <Workbench return <Workbench
key='workbench' key='workbench'
showRouteActionsSetting={showRouteActionsSetting}
model={model?.model} model={model?.model}
showSourcesFirst={true} showSourcesFirst={true}
rootDir={rootDir} rootDir={rootDir}

View file

@ -18,6 +18,15 @@
background-color: var(--vscode-sideBar-background); background-color: var(--vscode-sideBar-background);
} }
.ui-mode-sidebar > .settings-toolbar {
border-top: 1px solid var(--vscode-panel-border);
cursor: pointer;
}
.ui-mode-sidebar > .settings-view {
margin: 0 0 3px 23px;
}
.ui-mode-sidebar input[type=search] { .ui-mode-sidebar input[type=search] {
flex: auto; flex: auto;
} }

View file

@ -29,7 +29,7 @@ import { ToolbarButton } from '@web/components/toolbarButton';
import { Toolbar } from '@web/components/toolbar'; import { Toolbar } from '@web/components/toolbar';
import type { XtermDataSource } from '@web/components/xtermWrapper'; import type { XtermDataSource } from '@web/components/xtermWrapper';
import { XtermWrapper } from '@web/components/xtermWrapper'; import { XtermWrapper } from '@web/components/xtermWrapper';
import { toggleTheme } from '@web/theme'; import { useDarkModeSetting } from '@web/theme';
import { settings, useSetting } from '@web/uiUtils'; import { settings, useSetting } from '@web/uiUtils';
import { statusEx, TestTree } from '@testIsomorphic/testTree'; import { statusEx, TestTree } from '@testIsomorphic/testTree';
import type { TreeItem } from '@testIsomorphic/testTree'; import type { TreeItem } from '@testIsomorphic/testTree';
@ -39,6 +39,7 @@ import type { TestModel } from './uiModeModel';
import { FiltersView } from './uiModeFiltersView'; import { FiltersView } from './uiModeFiltersView';
import { TestListView } from './uiModeTestListView'; import { TestListView } from './uiModeTestListView';
import { TraceView } from './uiModeTraceView'; import { TraceView } from './uiModeTraceView';
import { SettingsView } from './settingsView';
let xtermSize = { cols: 80, rows: 24 }; let xtermSize = { cols: 80, rows: 24 };
const xtermDataSource: XtermDataSource = { const xtermDataSource: XtermDataSource = {
@ -94,6 +95,37 @@ export const UIModeView: React.FC<{}> = ({
const [hasBrowsers, setHasBrowsers] = React.useState(true); const [hasBrowsers, setHasBrowsers] = React.useState(true);
const [testServerConnection, setTestServerConnection] = React.useState<TestServerConnection>(); const [testServerConnection, setTestServerConnection] = React.useState<TestServerConnection>();
const [teleSuiteUpdater, setTeleSuiteUpdater] = React.useState<TeleSuiteUpdater>(); const [teleSuiteUpdater, setTeleSuiteUpdater] = React.useState<TeleSuiteUpdater>();
const [settingsVisible, setSettingsVisible] = React.useState(false);
const [testingOptionsVisible, setTestingOptionsVisible] = React.useState(false);
const [runWorkers, setRunWorkers] = React.useState(queryParams.workers);
const singleWorkerSetting = React.useMemo(() => {
return [
runWorkers === '1',
(value: boolean) => {
// When started with `--workers=1`, the setting allows to undo that.
// Otherwise, fallback to the cli `--workers=X` argument.
setRunWorkers(value ? '1' : (queryParams.workers === '1' ? undefined : queryParams.workers));
},
'Single worker',
] as const;
}, [runWorkers, setRunWorkers]);
const [runHeaded, setRunHeaded] = React.useState(queryParams.headed);
const showBrowserSetting = React.useMemo(() => [runHeaded, setRunHeaded, 'Show browser'] as const, [runHeaded, setRunHeaded]);
const [runUpdateSnapshots, setRunUpdateSnapshots] = React.useState(queryParams.updateSnapshots);
const updateSnapshotsSetting = React.useMemo(() => {
return [
runUpdateSnapshots === 'all',
(value: boolean) => setRunUpdateSnapshots(value ? 'all' : 'missing'),
'Update snapshots',
] as const;
}, [runUpdateSnapshots, setRunUpdateSnapshots]);
const [, , showRouteActionsSetting] = useSetting('show-route-actions', true, 'Show route actions');
const darkModeSetting = useDarkModeSetting();
const inputRef = React.useRef<HTMLInputElement>(null); const inputRef = React.useRef<HTMLInputElement>(null);
@ -294,11 +326,11 @@ export const UIModeView: React.FC<{}> = ({
grepInvert: queryParams.grepInvert, grepInvert: queryParams.grepInvert,
testIds: [...testIds], testIds: [...testIds],
projects: [...projectFilters].filter(([_, v]) => v).map(([p]) => p), projects: [...projectFilters].filter(([_, v]) => v).map(([p]) => p),
workers: queryParams.workers, workers: runWorkers,
timeout: queryParams.timeout, timeout: queryParams.timeout,
headed: queryParams.headed, headed: runHeaded,
outputDir: queryParams.outputDir, outputDir: queryParams.outputDir,
updateSnapshots: queryParams.updateSnapshots, updateSnapshots: runUpdateSnapshots,
reporters: queryParams.reporters, reporters: queryParams.reporters,
trace: 'on', trace: 'on',
}); });
@ -310,7 +342,7 @@ export const UIModeView: React.FC<{}> = ({
setTestModel({ ...testModel }); setTestModel({ ...testModel });
setRunningState(undefined); setRunningState(undefined);
}); });
}, [projectFilters, runningState, testModel, testServerConnection]); }, [projectFilters, runningState, testModel, testServerConnection, runWorkers, runHeaded, runUpdateSnapshots]);
// Watch implementation. // Watch implementation.
React.useEffect(() => { React.useEffect(() => {
@ -418,14 +450,13 @@ export const UIModeView: React.FC<{}> = ({
<XtermWrapper source={xtermDataSource}></XtermWrapper> <XtermWrapper source={xtermDataSource}></XtermWrapper>
</div> </div>
<div className={'vbox' + (isShowingOutput ? ' hidden' : '')}> <div className={'vbox' + (isShowingOutput ? ' hidden' : '')}>
<TraceView item={selectedItem} rootDir={testModel?.config?.rootDir} /> <TraceView showRouteActionsSetting={showRouteActionsSetting} item={selectedItem} rootDir={testModel?.config?.rootDir} />
</div> </div>
</div> </div>
<div className='vbox ui-mode-sidebar'> <div className='vbox ui-mode-sidebar'>
<Toolbar noShadow={true} noMinHeight={true}> <Toolbar noShadow={true} noMinHeight={true}>
<img src='playwright-logo.svg' alt='Playwright logo' /> <img src='playwright-logo.svg' alt='Playwright logo' />
<div className='section-title'>Playwright</div> <div className='section-title'>Playwright</div>
<ToolbarButton icon='color-mode' title='Toggle color mode' onClick={() => toggleTheme()} />
<ToolbarButton icon='refresh' title='Reload' onClick={() => reloadTests()} disabled={isRunningTest || isLoading}></ToolbarButton> <ToolbarButton icon='refresh' title='Reload' onClick={() => reloadTests()} disabled={isRunningTest || isLoading}></ToolbarButton>
<ToolbarButton icon='terminal' title={'Toggle output — ' + (isMac ? '⌃`' : 'Ctrl + `')} toggled={isShowingOutput} onClick={() => { setIsShowingOutput(!isShowingOutput); }} /> <ToolbarButton icon='terminal' title={'Toggle output — ' + (isMac ? '⌃`' : 'Ctrl + `')} toggled={isShowingOutput} onClick={() => { setIsShowingOutput(!isShowingOutput); }} />
{!hasBrowsers && <ToolbarButton icon='lightbulb-autofix' style={{ color: 'var(--vscode-list-warningForeground)' }} title='Playwright browsers are missing' onClick={openInstallDialog} />} {!hasBrowsers && <ToolbarButton icon='lightbulb-autofix' style={{ color: 'var(--vscode-list-warningForeground)' }} title='Playwright browsers are missing' onClick={openInstallDialog} />}
@ -472,6 +503,31 @@ export const UIModeView: React.FC<{}> = ({
requestedCollapseAllCount={collapseAllCount} requestedCollapseAllCount={collapseAllCount}
setFilterText={setFilterText} setFilterText={setFilterText}
/> />
<Toolbar noShadow={true} noMinHeight={true} className='settings-toolbar' onClick={() => setTestingOptionsVisible(!testingOptionsVisible)}>
<span
className={`codicon codicon-${testingOptionsVisible ? 'chevron-down' : 'chevron-right'}`}
style={{ marginLeft: 5 }}
title={testingOptionsVisible ? 'Hide Testing Options' : 'Show Testing Options'}
/>
<div className='section-title'>Testing Options</div>
</Toolbar>
{testingOptionsVisible && <SettingsView settings={[
singleWorkerSetting,
showBrowserSetting,
updateSnapshotsSetting,
]} />}
<Toolbar noShadow={true} noMinHeight={true} className='settings-toolbar' onClick={() => setSettingsVisible(!settingsVisible)}>
<span
className={`codicon codicon-${settingsVisible ? 'chevron-down' : 'chevron-right'}`}
style={{ marginLeft: 5 }}
title={settingsVisible ? 'Hide Settings' : 'Show Settings'}
/>
<div className='section-title'>Settings</div>
</Toolbar>
{settingsVisible && <SettingsView settings={[
darkModeSetting,
showRouteActionsSetting,
]} />}
</div> </div>
</SplitView> </SplitView>
</div>; </div>;

View file

@ -36,7 +36,7 @@ import { AttachmentsTab } from './attachmentsTab';
import type { Boundaries } from '../geometry'; import type { Boundaries } from '../geometry';
import { InspectorTab } from './inspectorTab'; import { InspectorTab } from './inspectorTab';
import { ToolbarButton } from '@web/components/toolbarButton'; import { ToolbarButton } from '@web/components/toolbarButton';
import { useSetting, msToString } from '@web/uiUtils'; import { useSetting, msToString, type Setting } from '@web/uiUtils';
import type { Entry } from '@trace/har'; import type { Entry } from '@trace/har';
import './workbench.css'; import './workbench.css';
import { testStatusIcon, testStatusText } from './testUtils'; import { testStatusIcon, testStatusText } from './testUtils';
@ -53,8 +53,9 @@ export const Workbench: React.FunctionComponent<{
isLive?: boolean, isLive?: boolean,
status?: UITestStatus, status?: UITestStatus,
inert?: boolean, inert?: boolean,
showRouteActionsSetting?: Setting<boolean>,
openPage?: (url: string, target?: string) => Window | any, openPage?: (url: string, target?: string) => Window | any,
}> = ({ model, showSourcesFirst, rootDir, fallbackLocation, initialSelection, onSelectionChanged, isLive, status, inert, openPage }) => { }> = ({ showRouteActionsSetting, model, showSourcesFirst, rootDir, fallbackLocation, initialSelection, onSelectionChanged, isLive, status, inert, openPage }) => {
const [selectedAction, setSelectedActionImpl] = React.useState<ActionTraceEventInContext | undefined>(undefined); const [selectedAction, setSelectedActionImpl] = React.useState<ActionTraceEventInContext | undefined>(undefined);
const [revealedStack, setRevealedStack] = React.useState<StackFrame[] | undefined>(undefined); const [revealedStack, setRevealedStack] = React.useState<StackFrame[] | undefined>(undefined);
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEventInContext | undefined>(); const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEventInContext | undefined>();
@ -67,7 +68,11 @@ export const Workbench: React.FunctionComponent<{
const activeAction = model ? highlightedAction || selectedAction : undefined; const activeAction = model ? highlightedAction || selectedAction : undefined;
const [selectedTime, setSelectedTime] = React.useState<Boundaries | undefined>(); const [selectedTime, setSelectedTime] = React.useState<Boundaries | undefined>();
const [sidebarLocation, setSidebarLocation] = useSetting<'bottom' | 'right'>('propertiesSidebarLocation', 'bottom'); const [sidebarLocation, setSidebarLocation] = useSetting<'bottom' | 'right'>('propertiesSidebarLocation', 'bottom');
const [showRouteActions, , showRouteActionsSetting] = useSetting('show-route-actions', true, 'Show route actions'); const [, , showRouteActionsSettingInternal] = useSetting(showRouteActionsSetting ? undefined : 'show-route-actions', true, 'Show route actions');
const showSettings = !showRouteActionsSetting;
showRouteActionsSetting ||= showRouteActionsSettingInternal;
const showRouteActions = showRouteActionsSetting[0];
const filteredActions = React.useMemo(() => { const filteredActions = React.useMemo(() => {
return (model?.actions || []).filter(action => showRouteActions || action.class !== 'Route'); return (model?.actions || []).filter(action => showRouteActions || action.class !== 'Route');
@ -229,6 +234,40 @@ export const Workbench: React.FunctionComponent<{
else if (model && model.wallTime) else if (model && model.wallTime)
time = Date.now() - model.wallTime; time = Date.now() - model.wallTime;
const actionsTab: TabbedPaneTabModel = {
id: 'actions',
title: 'Actions',
component: <div className='vbox'>
{status && <div className='workbench-run-status'>
<span className={`codicon ${testStatusIcon(status)}`}></span>
<div>{testStatusText(status)}</div>
<div className='spacer'></div>
<div className='workbench-run-duration'>{time ? msToString(time) : ''}</div>
</div>}
<ActionList
sdkLanguage={sdkLanguage}
actions={filteredActions}
selectedAction={model ? selectedAction : undefined}
selectedTime={selectedTime}
setSelectedTime={setSelectedTime}
onSelected={onActionSelected}
onHighlighted={setHighlightedAction}
revealConsole={() => selectPropertiesTab('console')}
isLive={isLive}
/>
</div>
};
const metadataTab: TabbedPaneTabModel = {
id: 'metadata',
title: 'Metadata',
component: <MetadataView model={model}/>
};
const settingsTab: TabbedPaneTabModel = {
id: 'settings',
title: 'Settings',
component: <SettingsView settings={[showRouteActionsSetting]}/>,
};
return <div className='vbox workbench' {...(inert ? { inert: 'true' } : {})}> return <div className='vbox workbench' {...(inert ? { inert: 'true' } : {})}>
<Timeline <Timeline
model={model} model={model}
@ -254,41 +293,7 @@ export const Workbench: React.FunctionComponent<{
setHighlightedLocator={locatorPicked} setHighlightedLocator={locatorPicked}
openPage={openPage} /> openPage={openPage} />
<TabbedPane <TabbedPane
tabs={[ tabs={showSettings ? [actionsTab, metadataTab, settingsTab] : [actionsTab, metadataTab]}
{
id: 'actions',
title: 'Actions',
component: <div className='vbox'>
{status && <div className='workbench-run-status'>
<span className={`codicon ${testStatusIcon(status)}`}></span>
<div>{testStatusText(status)}</div>
<div className='spacer'></div>
<div className='workbench-run-duration'>{time ? msToString(time) : ''}</div>
</div>}
<ActionList
sdkLanguage={sdkLanguage}
actions={filteredActions}
selectedAction={model ? selectedAction : undefined}
selectedTime={selectedTime}
setSelectedTime={setSelectedTime}
onSelected={onActionSelected}
onHighlighted={setHighlightedAction}
revealConsole={() => selectPropertiesTab('console')}
isLive={isLive}
/>
</div>
},
{
id: 'metadata',
title: 'Metadata',
component: <MetadataView model={model}/>
},
{
id: 'settings',
title: 'Settings',
component: <SettingsView settings={[showRouteActionsSetting]}/>,
}
]}
selectedTab={selectedNavigatorTab} selectedTab={selectedNavigatorTab}
setSelectedTab={setSelectedNavigatorTab} setSelectedTab={setSelectedNavigatorTab}
/> />

View file

@ -24,6 +24,7 @@ export type Size = { width: number, height: number };
export type VERSION = 7; export type VERSION = 7;
export type BrowserContextEventOptions = { export type BrowserContextEventOptions = {
baseURL?: string,
viewport?: Size, viewport?: Size,
deviceScaleFactor?: number, deviceScaleFactor?: number,
isMobile?: boolean, isMobile?: boolean,

View file

@ -20,12 +20,16 @@ import * as React from 'react';
type ToolbarProps = { type ToolbarProps = {
noShadow?: boolean; noShadow?: boolean;
noMinHeight?: boolean; noMinHeight?: boolean;
className?: string;
onClick?: (e: React.MouseEvent) => void;
}; };
export const Toolbar: React.FC<React.PropsWithChildren<ToolbarProps>> = ({ export const Toolbar: React.FC<React.PropsWithChildren<ToolbarProps>> = ({
noShadow, noShadow,
children, children,
noMinHeight noMinHeight,
className,
onClick,
}) => { }) => {
return <div className={'toolbar' + (noShadow ? ' no-shadow' : '') + (noMinHeight ? ' no-min-height' : '')}>{children}</div>; return <div className={'toolbar' + (noShadow ? ' no-shadow' : '') + (noMinHeight ? ' no-min-height' : '') + ' ' + (className || '')} onClick={onClick}>{children}</div>;
}; };

View file

@ -14,7 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { settings } from './uiUtils'; import React from 'react';
import { type Setting, settings } from './uiUtils';
export function applyTheme() { export function applyTheme() {
if ((document as any).playwrightThemeInitialized) if ((document as any).playwrightThemeInitialized)
@ -64,3 +65,13 @@ export function removeThemeListener(listener: (theme: Theme) => void) {
export function currentTheme(): Theme { export function currentTheme(): Theme {
return document.body.classList.contains('dark-mode') ? 'dark-mode' : 'light-mode'; return document.body.classList.contains('dark-mode') ? 'dark-mode' : 'light-mode';
} }
export function useDarkModeSetting() {
const [theme, setTheme] = React.useState(currentTheme() === 'dark-mode');
return [theme, (value: boolean) => {
const current = currentTheme() === 'dark-mode';
if (current !== value)
toggleTheme();
setTheme(value);
}, 'Dark mode'] as Setting<boolean>;
}

View file

@ -139,12 +139,7 @@ export function copy(text: string) {
textArea.remove(); textArea.remove();
} }
export type Setting<T> = { export type Setting<T> = readonly [T, (value: T) => void, string];
value: T;
set: (value: T) => void;
name: string;
title: string;
};
export function useSetting<S>(name: string | undefined, defaultValue: S, title?: string): [S, React.Dispatch<React.SetStateAction<S>>, Setting<S>] { export function useSetting<S>(name: string | undefined, defaultValue: S, title?: string): [S, React.Dispatch<React.SetStateAction<S>>, Setting<S>] {
if (name) if (name)
@ -155,12 +150,7 @@ export function useSetting<S>(name: string | undefined, defaultValue: S, title?:
settings.setObject(name, value); settings.setObject(name, value);
setValue(value); setValue(value);
}, [name, setValue]); }, [name, setValue]);
const setting = { const setting = [value, setValueWrapper, title || name || ''] as Setting<S>;
value,
set: setValueWrapper,
name: name || '',
title: title || name || '',
};
return [value, setValueWrapper, setting]; return [value, setValueWrapper, setting];
} }

View file

@ -44,6 +44,7 @@ class TraceViewerPage {
consoleStacks: Locator; consoleStacks: Locator;
stackFrames: Locator; stackFrames: Locator;
networkRequests: Locator; networkRequests: Locator;
metadataTab: Locator;
snapshotContainer: Locator; snapshotContainer: Locator;
constructor(public page: Page) { constructor(public page: Page) {
@ -57,6 +58,7 @@ class TraceViewerPage {
this.stackFrames = page.getByTestId('stack-trace-list').locator('.list-view-entry'); this.stackFrames = page.getByTestId('stack-trace-list').locator('.list-view-entry');
this.networkRequests = page.getByTestId('network-list').locator('.list-view-entry'); this.networkRequests = page.getByTestId('network-list').locator('.list-view-entry');
this.snapshotContainer = page.locator('.snapshot-container iframe.snapshot-visible[name=snapshot]'); this.snapshotContainer = page.locator('.snapshot-container iframe.snapshot-visible[name=snapshot]');
this.metadataTab = page.locator('.metadata-view');
} }
async actionIconsText(action: string) { async actionIconsText(action: string) {
@ -93,6 +95,10 @@ class TraceViewerPage {
await this.page.click('text="Network"'); await this.page.click('text="Network"');
} }
async showMetadataTab() {
await this.page.click('text="Metadata"');
}
@step @step
async snapshotFrame(actionName: string, ordinal: number = 0, hasSubframe: boolean = false): Promise<FrameLocator> { async snapshotFrame(actionName: string, ordinal: number = 0, hasSubframe: boolean = false): Promise<FrameLocator> {
await this.selectAction(actionName, ordinal); await this.selectAction(actionName, ordinal);

View file

@ -35,8 +35,12 @@ const __testHookLookup = (hostname: string): LookupAddress[] => {
let interceptedHostnameLookup: string | undefined; let interceptedHostnameLookup: string | undefined;
it.beforeEach(() => { it.beforeEach(({ server }) => {
interceptedHostnameLookup = undefined; interceptedHostnameLookup = undefined;
// Force a new connection every time, so that we can intercept the hostname lookup.
server.setExtraHeaders('/simple.json', {
'Connection': 'close',
});
}); });
it('get should work', async ({ context, server }) => { it('get should work', async ({ context, server }) => {

View file

@ -181,7 +181,7 @@ browserTest('should be able to get correct orientation angle on non-mobile devic
it('should set window.screen.orientation.type for mobile devices', async ({ contextFactory, browserName, server, isMac }) => { it('should set window.screen.orientation.type for mobile devices', async ({ contextFactory, browserName, server, isMac }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31151' }); it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31151' });
it.skip(browserName === 'firefox', 'Firefox does not support mobile emulation'); it.skip(browserName === 'firefox', 'Firefox does not support mobile emulation');
it.skip(isMac && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS 12 is frozen and does not support orientation.type override'); it.skip(browserName === 'webkit' && isMac && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS 12 is frozen and does not support orientation.type override');
const context = await contextFactory(devices['iPhone 14']); const context = await contextFactory(devices['iPhone 14']);
const page = await context.newPage(); const page = await context.newPage();
await page.goto(server.PREFIX + '/index.html'); await page.goto(server.PREFIX + '/index.html');

View file

@ -408,6 +408,29 @@ for (const kind of ['launchServer', 'run-server'] as const) {
} }
}); });
test('should reject waitForEvent before browser.close finishes', async ({ connect, startRemoteServer, server }) => {
const remoteServer = await startRemoteServer(kind);
const browser = await connect(remoteServer.wsEndpoint());
const newPage = await browser.newPage();
let rejected = false;
const promise = newPage.waitForEvent('download').catch(() => rejected = true);
await browser.close();
expect(rejected).toBe(true);
await promise;
});
test('should reject waitForEvent before browser.onDisconnect fires', async ({ connect, startRemoteServer, server }) => {
const remoteServer = await startRemoteServer(kind);
const browser = await connect(remoteServer.wsEndpoint());
const newPage = await browser.newPage();
const log: string[] = [];
const promise = newPage.waitForEvent('download').catch(() => log.push('rejected'));
browser.on('disconnected', () => log.push('disconnected'));
await remoteServer.close();
await promise;
await expect.poll(() => log).toEqual(['rejected', 'disconnected']);
});
test('should respect selectors', async ({ playwright, connect, startRemoteServer }) => { test('should respect selectors', async ({ playwright, connect, startRemoteServer }) => {
const remoteServer = await startRemoteServer(kind); const remoteServer = await startRemoteServer(kind);

View file

@ -19,7 +19,8 @@ import url from 'url';
import { contextTest as it, expect } from '../config/browserTest'; import { contextTest as it, expect } from '../config/browserTest';
import { hostPlatform } from '../../packages/playwright-core/src/utils/hostPlatform'; import { hostPlatform } from '../../packages/playwright-core/src/utils/hostPlatform';
it('SharedArrayBuffer should work @smoke', async function({ contextFactory, httpsServer }) { it('SharedArrayBuffer should work @smoke', async function({ contextFactory, httpsServer, isMac, browserName }) {
it.skip(browserName === 'webkit' && isMac && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS 12 is frozen and does not support SharedArrayBuffer');
const context = await contextFactory({ ignoreHTTPSErrors: true }); const context = await contextFactory({ ignoreHTTPSErrors: true });
const page = await context.newPage(); const page = await context.newPage();
httpsServer.setRoute('/sharedarraybuffer', (req, res) => { httpsServer.setRoute('/sharedarraybuffer', (req, res) => {

View file

@ -15,26 +15,27 @@
*/ */
import fs from 'fs'; import fs from 'fs';
import http2 from 'http2'; import type http2 from 'http2';
import type http from 'http'; import type http from 'http';
import { expect, playwrightTest as base } from '../config/browserTest'; import { expect, playwrightTest as base } from '../config/browserTest';
import type net from 'net'; import type net from 'net';
import type { BrowserContextOptions } from 'packages/playwright-test'; import type { BrowserContextOptions } from 'packages/playwright-test';
const { createHttpsServer } = require('../../packages/playwright-core/lib/utils'); const { createHttpsServer, createHttp2Server } = require('../../packages/playwright-core/lib/utils');
type TestOptions = { type TestOptions = {
startCCServer(options?: { startCCServer(options?: {
http2?: boolean; http2?: boolean;
enableHTTP1FallbackWhenUsingHttp2?: boolean;
useFakeLocalhost?: boolean; useFakeLocalhost?: boolean;
}): Promise<string>, }): Promise<string>,
}; };
const test = base.extend<TestOptions>({ const test = base.extend<TestOptions>({
startCCServer: async ({ asset, browserName }, use) => { startCCServer: async ({ asset }, use) => {
process.env.PWTEST_UNSUPPORTED_CUSTOM_CA = asset('client-certificates/server/server_cert.pem'); process.env.PWTEST_UNSUPPORTED_CUSTOM_CA = asset('client-certificates/server/server_cert.pem');
let server: http.Server | http2.Http2Server | undefined; let server: http.Server | http2.Http2SecureServer | undefined;
await use(async options => { await use(async options => {
server = (options?.http2 ? http2.createSecureServer : createHttpsServer)({ server = (options?.http2 ? createHttp2Server : createHttpsServer)({
key: fs.readFileSync(asset('client-certificates/server/server_key.pem')), key: fs.readFileSync(asset('client-certificates/server/server_key.pem')),
cert: fs.readFileSync(asset('client-certificates/server/server_cert.pem')), cert: fs.readFileSync(asset('client-certificates/server/server_cert.pem')),
ca: [ ca: [
@ -42,23 +43,25 @@ const test = base.extend<TestOptions>({
], ],
requestCert: true, requestCert: true,
rejectUnauthorized: false, rejectUnauthorized: false,
allowHTTP1: true, allowHTTP1: options?.enableHTTP1FallbackWhenUsingHttp2,
}, (req: (http2.Http2ServerRequest | http.IncomingMessage), res: http2.Http2ServerResponse | http.ServerResponse) => { }, (req: (http2.Http2ServerRequest | http.IncomingMessage), res: http2.Http2ServerResponse | http.ServerResponse) => {
const tlsSocket = req.socket as import('tls').TLSSocket; const tlsSocket = req.socket as import('tls').TLSSocket;
const parts: { key: string, value: any }[] = [];
parts.push({ key: 'alpn-protocol', value: tlsSocket.alpnProtocol });
// @ts-expect-error https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/62336 // @ts-expect-error https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/62336
expect(['localhost', 'local.playwright'].includes((tlsSocket).servername)).toBe(true); parts.push({ key: 'servername', value: tlsSocket.servername });
const prefix = `ALPN protocol: ${tlsSocket.alpnProtocol}\n`;
const cert = tlsSocket.getPeerCertificate(); const cert = tlsSocket.getPeerCertificate();
if (tlsSocket.authorized) { if (tlsSocket.authorized) {
res.writeHead(200, { 'Content-Type': 'text/html' }); res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(prefix + `Hello ${cert.subject.CN}, your certificate was issued by ${cert.issuer.CN}!`); parts.push({ key: 'message', value: `Hello ${cert.subject.CN}, your certificate was issued by ${cert.issuer.CN}!` });
} else if (cert.subject) { } else if (cert.subject) {
res.writeHead(403, { 'Content-Type': 'text/html' }); res.writeHead(403, { 'Content-Type': 'text/html' });
res.end(prefix + `Sorry ${cert.subject.CN}, certificates from ${cert.issuer.CN} are not welcome here.`); parts.push({ key: 'message', value: `Sorry ${cert.subject.CN}, certificates from ${cert.issuer.CN} are not welcome here.` });
} else { } else {
res.writeHead(401, { 'Content-Type': 'text/html' }); res.writeHead(401, { 'Content-Type': 'text/html' });
res.end(prefix + `Sorry, but you need to provide a client certificate to continue.`); parts.push({ key: 'message', value: `Sorry, but you need to provide a client certificate to continue.` });
} }
res.end(parts.map(({ key, value }) => `<div data-testid="${key}">${value}</div>`).join(''));
}); });
await new Promise<void>(f => server.listen(0, 'localhost', () => f())); await new Promise<void>(f => server.listen(0, 'localhost', () => f()));
const host = options?.useFakeLocalhost ? 'local.playwright' : 'localhost'; const host = options?.useFakeLocalhost ? 'local.playwright' : 'localhost';
@ -179,7 +182,7 @@ test.describe('fetch', () => {
await route.fulfill({ response }); await route.fulfill({ response });
}); });
await page.goto(serverURL); await page.goto(serverURL);
await expect(page.getByText('Hello Alice, your certificate was issued by localhost!')).toBeVisible(); await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!');
await page.close(); await page.close();
await request.dispose(); await request.dispose();
}); });
@ -216,7 +219,7 @@ test.describe('browser', () => {
}], }],
}); });
await page.goto(serverURL); await page.goto(serverURL);
await expect(page.getByText('Sorry, but you need to provide a client certificate to continue.')).toBeVisible(); await expect(page.getByTestId('message')).toHaveText('Sorry, but you need to provide a client certificate to continue.');
await page.close(); await page.close();
}); });
@ -230,7 +233,7 @@ test.describe('browser', () => {
}], }],
}); });
await page.goto(serverURL); await page.goto(serverURL);
await expect(page.getByText('Sorry Bob, certificates from Bob are not welcome here')).toBeVisible(); await expect(page.getByTestId('message')).toHaveText('Sorry Bob, certificates from Bob are not welcome here.');
await page.close(); await page.close();
}); });
@ -244,6 +247,20 @@ test.describe('browser', () => {
}], }],
}); });
await page.goto(serverURL); await page.goto(serverURL);
await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!');
await page.close();
});
test('should pass with matching certificates and trailing slash', async ({ browser, startCCServer, asset, browserName }) => {
const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' });
const page = await browser.newPage({
clientCertificates: [{
origin: serverURL,
certPath: asset('client-certificates/client/trusted/cert.pem'),
keyPath: asset('client-certificates/client/trusted/key.pem'),
}],
});
await page.goto(serverURL);
await expect(page.getByText('Hello Alice, your certificate was issued by localhost!')).toBeVisible(); await expect(page.getByText('Hello Alice, your certificate was issued by localhost!')).toBeVisible();
await page.close(); await page.close();
}); });
@ -263,7 +280,8 @@ test.describe('browser', () => {
test('support http2', async ({ browser, startCCServer, asset, browserName }) => { test('support http2', async ({ browser, startCCServer, asset, browserName }) => {
test.skip(browserName === 'webkit' && process.platform === 'darwin', 'WebKit on macOS doesn\n proxy localhost'); test.skip(browserName === 'webkit' && process.platform === 'darwin', 'WebKit on macOS doesn\n proxy localhost');
const serverURL = await startCCServer({ http2: true }); const enableHTTP1FallbackWhenUsingHttp2 = browserName === 'webkit' && process.platform === 'linux';
const serverURL = await startCCServer({ http2: true, enableHTTP1FallbackWhenUsingHttp2 });
const page = await browser.newPage({ const page = await browser.newPage({
clientCertificates: [{ clientCertificates: [{
origin: new URL(serverURL).origin, origin: new URL(serverURL).origin,
@ -273,23 +291,24 @@ test.describe('browser', () => {
}); });
// TODO: We should investigate why http2 is not supported in WebKit on Linux. // TODO: We should investigate why http2 is not supported in WebKit on Linux.
// https://bugs.webkit.org/show_bug.cgi?id=276990 // https://bugs.webkit.org/show_bug.cgi?id=276990
const expectedProtocol = browserName === 'webkit' && process.platform === 'linux' ? 'http/1.1' : 'h2'; const expectedProtocol = enableHTTP1FallbackWhenUsingHttp2 ? 'http/1.1' : 'h2';
{ {
await page.goto(serverURL.replace('localhost', 'local.playwright')); await page.goto(serverURL.replace('localhost', 'local.playwright'));
await expect(page.getByText('Sorry, but you need to provide a client certificate to continue.')).toBeVisible(); await expect(page.getByTestId('message')).toHaveText('Sorry, but you need to provide a client certificate to continue.');
await expect(page.getByText(`ALPN protocol: ${expectedProtocol}`)).toBeVisible(); await expect(page.getByTestId('alpn-protocol')).toHaveText(expectedProtocol);
await expect(page.getByTestId('servername')).toHaveText('local.playwright');
} }
{ {
await page.goto(serverURL); await page.goto(serverURL);
await expect(page.getByText('Hello Alice, your certificate was issued by localhost!')).toBeVisible(); await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!');
await expect(page.getByText(`ALPN protocol: ${expectedProtocol}`)).toBeVisible(); await expect(page.getByTestId('alpn-protocol')).toHaveText(expectedProtocol);
} }
await page.close(); await page.close();
}); });
test('support http2 if the browser only supports http1.1', async ({ browserType, browserName, startCCServer, asset }) => { test('support http2 if the browser only supports http1.1', async ({ browserType, browserName, startCCServer, asset }) => {
test.skip(browserName !== 'chromium'); test.skip(browserName !== 'chromium');
const serverURL = await startCCServer({ http2: true }); const serverURL = await startCCServer({ http2: true, enableHTTP1FallbackWhenUsingHttp2: true });
const browser = await browserType.launch({ args: ['--disable-http2'] }); const browser = await browserType.launch({ args: ['--disable-http2'] });
const page = await browser.newPage({ const page = await browser.newPage({
clientCertificates: [{ clientCertificates: [{
@ -300,17 +319,36 @@ test.describe('browser', () => {
}); });
{ {
await page.goto(serverURL.replace('localhost', 'local.playwright')); await page.goto(serverURL.replace('localhost', 'local.playwright'));
await expect(page.getByText('Sorry, but you need to provide a client certificate to continue.')).toBeVisible(); await expect(page.getByTestId('message')).toHaveText('Sorry, but you need to provide a client certificate to continue.');
await expect(page.getByText('ALPN protocol: http/1.1')).toBeVisible(); await expect(page.getByTestId('alpn-protocol')).toHaveText('http/1.1');
} }
{ {
await page.goto(serverURL); await page.goto(serverURL);
await expect(page.getByText('Hello Alice, your certificate was issued by localhost!')).toBeVisible(); await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!');
await expect(page.getByText('ALPN protocol: http/1.1')).toBeVisible(); await expect(page.getByTestId('alpn-protocol')).toHaveText('http/1.1');
} }
await browser.close(); await browser.close();
}); });
test('should return target connection errors when using http2', async ({ browser, startCCServer, asset, browserName }) => {
test.skip(browserName === 'webkit' && process.platform === 'darwin', 'WebKit on macOS doesn\n proxy localhost');
test.fixme(browserName === 'webkit' && process.platform === 'linux', 'WebKit on Linux does not support http2 https://bugs.webkit.org/show_bug.cgi?id=276990');
test.skip(+process.versions.node.split('.')[0] < 20, 'http2.performServerHandshake is not supported in older Node.js versions');
process.env.PWTEST_UNSUPPORTED_CUSTOM_CA = asset('empty.html');
const serverURL = await startCCServer({ http2: true });
const page = await browser.newPage({
clientCertificates: [{
origin: 'https://just-there-that-the-client-certificates-proxy-server-is-getting-launched.com',
certPath: asset('client-certificates/client/trusted/cert.pem'),
keyPath: asset('client-certificates/client/trusted/key.pem'),
}],
});
await page.goto(serverURL);
await expect(page.getByText('Playwright client-certificate error: self-signed certificate')).toBeVisible();
await page.close();
});
test.describe('persistentContext', () => { test.describe('persistentContext', () => {
test('validate input', async ({ launchPersistent }) => { test('validate input', async ({ launchPersistent }) => {
test.slow(); test.slow();
@ -328,7 +366,7 @@ test.describe('browser', () => {
}], }],
}); });
await page.goto(serverURL); await page.goto(serverURL);
await expect(page.getByText('Hello Alice, your certificate was issued by localhost!')).toBeVisible(); await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!');
}); });
}); });
}); });

View file

@ -18,11 +18,11 @@
import { browserTest as it, expect } from '../config/browserTest'; import { browserTest as it, expect } from '../config/browserTest';
import * as path from 'path'; import * as path from 'path';
import fs from 'fs'; import fs from 'fs';
import http2 from 'http2';
import type { BrowserContext, BrowserContextOptions } from 'playwright-core'; import type { BrowserContext, BrowserContextOptions } from 'playwright-core';
import type { AddressInfo } from 'net'; import type { AddressInfo } from 'net';
import type { Log } from '../../packages/trace/src/har'; import type { Log } from '../../packages/trace/src/har';
import { parseHar } from '../config/utils'; import { parseHar } from '../config/utils';
const { createHttp2Server } = require('../../packages/playwright-core/lib/utils');
async function pageWithHar(contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>, testInfo: any, options: { outputPath?: string, content?: 'embed' | 'attach' | 'omit', omitContent?: boolean } = {}) { async function pageWithHar(contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>, testInfo: any, options: { outputPath?: string, content?: 'embed' | 'attach' | 'omit', omitContent?: boolean } = {}) {
const harPath = testInfo.outputPath(options.outputPath || 'test.har'); const harPath = testInfo.outputPath(options.outputPath || 'test.har');
@ -686,7 +686,7 @@ it('should return security details directly from response', async ({ contextFact
}); });
it('should contain http2 for http2 requests', async ({ contextFactory }, testInfo) => { it('should contain http2 for http2 requests', async ({ contextFactory }, testInfo) => {
const server = http2.createSecureServer({ const server = createHttp2Server({
key: await fs.promises.readFile(path.join(__dirname, '..', 'config', 'testserver', 'key.pem')), key: await fs.promises.readFile(path.join(__dirname, '..', 'config', 'testserver', 'key.pem')),
cert: await fs.promises.readFile(path.join(__dirname, '..', 'config', 'testserver', 'cert.pem')), cert: await fs.promises.readFile(path.join(__dirname, '..', 'config', 'testserver', 'cert.pem')),
}); });

View file

@ -559,7 +559,7 @@ await page.GetByLabel("Coun\\"try").ClickAsync();`);
]); ]);
}); });
test('should consume contextmenu events, despite a custom context menu', async ({ page, openRecorder }) => { test('should consume contextmenu events, despite a custom context menu', async ({ page, openRecorder, browserName, platform }) => {
const recorder = await openRecorder(); const recorder = await openRecorder();
await recorder.setContentAndWait(` await recorder.setContentAndWait(`
@ -597,6 +597,21 @@ await page.GetByLabel("Coun\\"try").ClickAsync();`);
recorder.trustedClick({ button: 'right' }), recorder.trustedClick({ button: 'right' }),
]); ]);
expect(message.text()).toBe('right-clicked'); expect(message.text()).toBe('right-clicked');
if (browserName === 'chromium' && platform === 'win32') {
expect(await page.evaluate('log')).toEqual([
// hover
'button: pointermove',
'button: mousemove',
// trusted right click
'button: pointermove',
'button: mousemove',
'button: pointerdown',
'button: mousedown',
'button: pointerup',
'button: mouseup',
'button: contextmenu',
]);
} else {
expect(await page.evaluate('log')).toEqual([ expect(await page.evaluate('log')).toEqual([
// hover // hover
'button: pointermove', 'button: pointermove',
@ -609,8 +624,9 @@ await page.GetByLabel("Coun\\"try").ClickAsync();`);
'button: mousedown', 'button: mousedown',
'button: contextmenu', 'button: contextmenu',
'menu: pointerup', 'menu: pointerup',
'menu: mouseup' 'menu: mouseup',
]); ]);
}
}); });
test('should assert value', async ({ openRecorder }) => { test('should assert value', async ({ openRecorder }) => {

View file

@ -31,7 +31,9 @@ test.slow();
let traceFile: string; let traceFile: string;
test.beforeAll(async function recordTrace({ browser, browserName, browserType, server }, workerInfo) { test.beforeAll(async function recordTrace({ browser, browserName, browserType, server }, workerInfo) {
const context = await browser.newContext(); const context = await browser.newContext({
baseURL: 'https://example.com',
});
await context.tracing.start({ name: 'test', screenshots: true, snapshots: true, sources: true }); await context.tracing.start({ name: 'test', screenshots: true, snapshots: true, sources: true });
const page = await context.newPage(); const page = await context.newPage();
await page.goto(`data:text/html,<!DOCTYPE html><html>Hello world</html>`); await page.goto(`data:text/html,<!DOCTYPE html><html>Hello world</html>`);
@ -1368,3 +1370,12 @@ test('should allow hiding route actions', {
/route.fulfill/, /route.fulfill/,
]); ]);
}); });
test('should show baseURL in metadata pane', {
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31847' },
}, async ({ showTraceViewer }) => {
const traceViewer = await showTraceViewer([traceFile]);
await traceViewer.selectAction('page.evaluate');
await traceViewer.showMetadataTab();
await expect(traceViewer.metadataTab).toContainText('baseURL:https://example.com');
});

View file

@ -39,7 +39,7 @@ it('should upload the file', async ({ page, server, asset }) => {
it('should upload a folder', async ({ page, server, browserName, headless, browserMajorVersion, isAndroid }) => { it('should upload a folder', async ({ page, server, browserName, headless, browserMajorVersion, isAndroid }) => {
it.skip(isAndroid); it.skip(isAndroid);
it.skip(os.platform() === 'darwin' && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS-12 is frozen'); it.skip(browserName === 'webkit' && os.platform() === 'darwin' && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS-12 is frozen');
await page.goto(server.PREFIX + '/input/folderupload.html'); await page.goto(server.PREFIX + '/input/folderupload.html');
const input = await page.$('input'); const input = await page.$('input');
@ -70,9 +70,9 @@ it('should upload a folder', async ({ page, server, browserName, headless, brows
} }
}); });
it('should upload a folder and throw for multiple directories', async ({ page, server, isAndroid }) => { it('should upload a folder and throw for multiple directories', async ({ page, server, isAndroid, browserName }) => {
it.skip(isAndroid); it.skip(isAndroid);
it.skip(os.platform() === 'darwin' && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS-12 is frozen'); it.skip(browserName === 'webkit' && os.platform() === 'darwin' && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS-12 is frozen');
await page.goto(server.PREFIX + '/input/folderupload.html'); await page.goto(server.PREFIX + '/input/folderupload.html');
const input = await page.$('input'); const input = await page.$('input');
@ -89,9 +89,9 @@ it('should upload a folder and throw for multiple directories', async ({ page, s
])).rejects.toThrow('Multiple directories are not supported'); ])).rejects.toThrow('Multiple directories are not supported');
}); });
it('should throw if a directory and files are passed', async ({ page, server, isAndroid }) => { it('should throw if a directory and files are passed', async ({ page, server, isAndroid, browserName }) => {
it.skip(isAndroid); it.skip(isAndroid);
it.skip(os.platform() === 'darwin' && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS-12 is frozen'); it.skip(browserName === 'webkit' && os.platform() === 'darwin' && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS-12 is frozen');
await page.goto(server.PREFIX + '/input/folderupload.html'); await page.goto(server.PREFIX + '/input/folderupload.html');
const input = await page.$('input'); const input = await page.$('input');
@ -106,9 +106,9 @@ it('should throw if a directory and files are passed', async ({ page, server, is
])).rejects.toThrow('File paths must be all files or a single directory'); ])).rejects.toThrow('File paths must be all files or a single directory');
}); });
it('should throw when uploading a folder in a normal file upload input', async ({ page, server, isAndroid }) => { it('should throw when uploading a folder in a normal file upload input', async ({ page, server, isAndroid, browserName }) => {
it.skip(isAndroid); it.skip(isAndroid);
it.skip(os.platform() === 'darwin' && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS-12 is frozen'); it.skip(browserName === 'webkit' && os.platform() === 'darwin' && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS-12 is frozen');
await page.goto(server.PREFIX + '/input/fileupload.html'); await page.goto(server.PREFIX + '/input/fileupload.html');
const input = await page.$('input'); const input = await page.$('input');
@ -120,9 +120,9 @@ it('should throw when uploading a folder in a normal file upload input', async (
await expect(input.setInputFiles(dir)).rejects.toThrow('File input does not support directories, pass individual files instead'); await expect(input.setInputFiles(dir)).rejects.toThrow('File input does not support directories, pass individual files instead');
}); });
it('should throw when uploading a file in a directory upload input', async ({ page, server, isAndroid, asset }) => { it('should throw when uploading a file in a directory upload input', async ({ page, server, isAndroid, asset, browserName }) => {
it.skip(isAndroid); it.skip(isAndroid);
it.skip(os.platform() === 'darwin' && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS-12 is frozen'); it.skip(browserName === 'webkit' && os.platform() === 'darwin' && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS-12 is frozen');
await page.goto(server.PREFIX + '/input/folderupload.html'); await page.goto(server.PREFIX + '/input/folderupload.html');
const input = await page.$('input'); const input = await page.$('input');

View file

@ -234,6 +234,7 @@ test('should only show tests selected with --grep', async ({ runUITest }) => {
const { page } = await runUITest(basicTestTree, undefined, { const { page } = await runUITest(basicTestTree, undefined, {
additionalArgs: ['--grep', 'fails'], additionalArgs: ['--grep', 'fails'],
}); });
await expect.poll(dumpTestTree(page)).toContain('fails');
await expect.poll(dumpTestTree(page)).not.toContain('passes'); await expect.poll(dumpTestTree(page)).not.toContain('passes');
}); });

View file

@ -45,7 +45,8 @@ test('should work after theme switch', async ({ runUITest, writeFiles }) => {
await page.getByTitle('Run all').click(); await page.getByTitle('Run all').click();
await expect(page.getByTestId('output')).toContainText(`Hello world 1`); await expect(page.getByTestId('output')).toContainText(`Hello world 1`);
await page.getByTitle('Toggle color mode').click(); await page.getByText('Settings', { exact: true }).click();
await page.getByLabel('Dark mode').click();
await writeFiles({ await writeFiles({
'a.test.ts': ` 'a.test.ts': `
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';

View file

@ -4,7 +4,7 @@ set -x
trap "cd $(pwd -P)" EXIT trap "cd $(pwd -P)" EXIT
SCRIPT_PATH="$(cd "$(dirname "$0")" ; pwd -P)" SCRIPT_PATH="$(cd "$(dirname "$0")" ; pwd -P)"
NODE_VERSION="20.15.1" # autogenerated via ./update-playwright-driver-version.mjs NODE_VERSION="20.16.0" # autogenerated via ./update-playwright-driver-version.mjs
cd "$(dirname "$0")" cd "$(dirname "$0")"
PACKAGE_VERSION=$(node -p "require('../../package.json').version") PACKAGE_VERSION=$(node -p "require('../../package.json').version")