Merge branch 'main' into sharding-seed
This commit is contained in:
commit
86422bb6d7
|
|
@ -1,6 +1,6 @@
|
|||
# 🎭 Playwright
|
||||
|
||||
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop -->
|
||||
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop -->
|
||||
|
||||
## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)
|
||||
|
||||
|
|
@ -8,9 +8,9 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr
|
|||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Chromium <!-- GEN:chromium-version -->125.0.6422.41<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Chromium <!-- GEN:chromium-version -->126.0.6478.8<!-- 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: |
|
||||
| Firefox <!-- GEN:firefox-version -->125.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Firefox <!-- GEN:firefox-version -->126.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.
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Playwright performs a range of actionability checks on the elements before makin
|
|||
behave as expected. It auto-waits for all the relevant checks to pass and only then performs the requested action. If the required checks do not pass within the given `timeout`, action fails with the `TimeoutError`.
|
||||
|
||||
For example, for [`method: Locator.click`], Playwright will ensure that:
|
||||
- locator resolves to an exactly one element
|
||||
- locator resolves to exactly one element
|
||||
- element is [Visible]
|
||||
- element is [Stable], as in not animating or completed animation
|
||||
- element [Receives Events], as in not obscured by other elements
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ pnpm create playwright
|
|||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
|
||||
|
|
@ -81,10 +82,40 @@ The `tests` folder contains a basic example test to help you get started with te
|
|||
|
||||
By default tests will be run on all 3 browsers, chromium, firefox and webkit using 3 workers. This can be configured in the [playwright.config file](./test-configuration.md). Tests are run in headless mode meaning no browser will open up when running the tests. Results of the tests and test logs will be shown in the terminal.
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'yarn', value: 'yarn'},
|
||||
{label: 'pnpm', value: 'pnpm'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npx playwright test
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn playwright test
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm exec playwright test
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||

|
||||
See our doc on [Running Tests](./running-tests.md) to learn more about running tests in headed mode, running multiple tests, running specific tests etc.
|
||||
|
||||
|
|
@ -92,19 +123,81 @@ See our doc on [Running Tests](./running-tests.md) to learn more about running t
|
|||
|
||||
After your test completes, an [HTML Reporter](./test-reporters.md#html-reporter) will be generated, which shows you a full report of your tests allowing you to filter the report by browsers, passed tests, failed tests, skipped tests and flaky tests. You can click on each test and explore the test's errors as well as each step of the test. By default, the HTML report is opened automatically if some of the tests failed.
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'yarn', value: 'yarn'},
|
||||
{label: 'pnpm', value: 'pnpm'}
|
||||
]
|
||||
}>
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npx playwright show-report
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn playwright show-report
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm exec playwright show-report
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||

|
||||
|
||||
## Running the Example Test in UI Mode
|
||||
|
||||
Run your tests with [UI Mode](./test-ui-mode.md) for a better developer experience with time travel debugging, watch mode and more.
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'yarn', value: 'yarn'},
|
||||
{label: 'pnpm', value: 'pnpm'}
|
||||
]
|
||||
}>
|
||||
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npx playwright test --ui
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn playwright test --ui
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm exec playwright test --ui
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||

|
||||
|
||||
Check out or [detailed guide on UI Mode](./test-ui-mode.md) to learn more about its features.
|
||||
|
|
@ -113,17 +206,84 @@ Check out or [detailed guide on UI Mode](./test-ui-mode.md) to learn more about
|
|||
|
||||
To update Playwright to the latest version run the following command:
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'yarn', value: 'yarn'},
|
||||
{label: 'pnpm', value: 'pnpm'}
|
||||
]
|
||||
}>
|
||||
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npm install -D @playwright/test@latest
|
||||
# Also download new browser binaries and their dependencies:
|
||||
npx playwright install --with-deps
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn add --dev @playwright/test@latest
|
||||
# Also download new browser binaries and their dependencies:
|
||||
yarn playwright install --with-deps
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm install --save-dev @playwright/test@latest
|
||||
# Also download new browser binaries and their dependencies:
|
||||
pnpm exec playwright install --with-deps
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
You can always check which version of Playwright you have by running the following command:
|
||||
|
||||
<Tabs
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'yarn', value: 'yarn'},
|
||||
{label: 'pnpm', value: 'pnpm'}
|
||||
]
|
||||
}>
|
||||
|
||||
<TabItem value="npm">
|
||||
|
||||
```bash
|
||||
npx playwright --version
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn">
|
||||
|
||||
```bash
|
||||
yarn playwright --version
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm exec playwright --version
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## System requirements
|
||||
|
||||
- Node.js 18+
|
||||
|
|
|
|||
|
|
@ -4,6 +4,82 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.44
|
||||
|
||||
### New APIs
|
||||
|
||||
**Accessibility assertions**
|
||||
|
||||
- [`method: LocatorAssertions.toHaveAccessibleName`] checks if the element has the specified accessible name:
|
||||
```csharp
|
||||
var locator = Page.GetByRole(AriaRole.Button);
|
||||
await Expect(locator).ToHaveAccessibleNameAsync("Submit");
|
||||
```
|
||||
|
||||
- [`method: LocatorAssertions.toHaveAccessibleDescription`] checks if the element has the specified accessible description:
|
||||
```csharp
|
||||
var locator = Page.GetByRole(AriaRole.Button);
|
||||
await Expect(locator).ToHaveAccessibleDescriptionAsync("Upload a photo");
|
||||
```
|
||||
|
||||
- [`method: LocatorAssertions.toHaveRole`] checks if the element has the specified ARIA role:
|
||||
```csharp
|
||||
var locator = Page.GetByTestId("save-button");
|
||||
await Expect(locator).ToHaveRoleAsync(AriaRole.Button);
|
||||
```
|
||||
|
||||
**Locator handler**
|
||||
|
||||
- After executing the handler added with [`method: Page.addLocatorHandler`], Playwright will now wait until the overlay that triggered the handler is not visible anymore. You can opt-out of this behavior with the new `NoWaitAfter` option.
|
||||
- You can use new `Times` option in [`method: Page.addLocatorHandler`] to specify maximum number of times the handler should be run.
|
||||
- The handler in [`method: Page.addLocatorHandler`] now accepts the locator as argument.
|
||||
- New [`method: Page.removeLocatorHandler`] method for removing previously added locator handlers.
|
||||
|
||||
```csharp
|
||||
var locator = Page.GetByText("This interstitial covers the button");
|
||||
await Page.AddLocatorHandlerAsync(locator, async (overlay) =>
|
||||
{
|
||||
await overlay.Locator("#close").ClickAsync();
|
||||
}, new() { Times = 3, NoWaitAfter = true });
|
||||
// Run your tests that can be interrupted by the overlay.
|
||||
// ...
|
||||
await Page.RemoveLocatorHandlerAsync(locator);
|
||||
```
|
||||
|
||||
**Miscellaneous options**
|
||||
|
||||
- New method [`method: FormData.append`] allows to specify repeating fields with the same name in [`Multipart`](./api/class-apirequestcontext#api-request-context-fetch-option-multipart) option in `APIRequestContext.FetchAsync()`:
|
||||
- ```
|
||||
```csharp
|
||||
var formData = Context.APIRequest.CreateFormData();
|
||||
formData.Append("file", new FilePayload()
|
||||
{
|
||||
Name = "f1.js",
|
||||
MimeType = "text/javascript",
|
||||
Buffer = System.Text.Encoding.UTF8.GetBytes("var x = 2024;")
|
||||
});
|
||||
formData.Append("file", new FilePayload()
|
||||
{
|
||||
Name = "f2.txt",
|
||||
MimeType = "text/plain",
|
||||
Buffer = System.Text.Encoding.UTF8.GetBytes("hello")
|
||||
});
|
||||
var response = await Context.APIRequest.PostAsync("https://example.com/uploadFiles", new() { Multipart = formData });
|
||||
```
|
||||
|
||||
- [`method: PageAssertions.toHaveURL`] now supports `IgnoreCase` [option](./api/class-pageassertions#page-assertions-to-have-url-option-ignore-case).
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 125.0.6422.14
|
||||
* Mozilla Firefox 125.0.1
|
||||
* WebKit 17.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 124
|
||||
* Microsoft Edge 124
|
||||
|
||||
## Version 1.43
|
||||
|
||||
### New APIs
|
||||
|
|
|
|||
|
|
@ -4,6 +4,72 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.44
|
||||
|
||||
### New APIs
|
||||
|
||||
**Accessibility assertions**
|
||||
|
||||
- [`method: LocatorAssertions.toHaveAccessibleName`] checks if the element has the specified accessible name:
|
||||
```java
|
||||
Locator locator = page.getByRole(AriaRole.BUTTON);
|
||||
assertThat(locator).hasAccessibleName("Submit");
|
||||
```
|
||||
|
||||
- [`method: LocatorAssertions.toHaveAccessibleDescription`] checks if the element has the specified accessible description:
|
||||
```java
|
||||
Locator locator = page.getByRole(AriaRole.BUTTON);
|
||||
assertThat(locator).hasAccessibleDescription("Upload a photo");
|
||||
```
|
||||
|
||||
- [`method: LocatorAssertions.toHaveRole`] checks if the element has the specified ARIA role:
|
||||
```java
|
||||
Locator locator = page.getByTestId("save-button");
|
||||
assertThat(locator).hasRole(AriaRole.BUTTON);
|
||||
```
|
||||
|
||||
**Locator handler**
|
||||
|
||||
- After executing the handler added with [`method: Page.addLocatorHandler`], Playwright will now wait until the overlay that triggered the handler is not visible anymore. You can opt-out of this behavior with the new `setNoWaitAfter` option.
|
||||
- You can use new `setTimes` option in [`method: Page.addLocatorHandler`] to specify maximum number of times the handler should be run.
|
||||
- The handler in [`method: Page.addLocatorHandler`] now accepts the locator as argument.
|
||||
- New [`method: Page.removeLocatorHandler`] method for removing previously added locator handlers.
|
||||
|
||||
```java
|
||||
Locator locator = page.getByText("This interstitial covers the button");
|
||||
page.addLocatorHandler(locator, overlay -> {
|
||||
overlay.locator("#close").click();
|
||||
}, new Page.AddLocatorHandlerOptions().setTimes(3).setNoWaitAfter(true));
|
||||
// Run your tests that can be interrupted by the overlay.
|
||||
// ...
|
||||
page.removeLocatorHandler(locator);
|
||||
```
|
||||
|
||||
**Miscellaneous options**
|
||||
|
||||
- New method [`method: FormData.append`] allows to specify repeating fields with the same name in [`setMultipart`](./api/class-requestoptions#request-options-set-multipart) option in `RequestOptions`:
|
||||
```java
|
||||
FormData formData = FormData.create();
|
||||
formData.append("file", new FilePayload("f1.js", "text/javascript",
|
||||
"var x = 2024;".getBytes(StandardCharsets.UTF_8)));
|
||||
formData.append("file", new FilePayload("f2.txt", "text/plain",
|
||||
"hello".getBytes(StandardCharsets.UTF_8)));
|
||||
APIResponse response = context.request().post("https://example.com/uploadFile", RequestOptions.create().setMultipart(formData));
|
||||
```
|
||||
|
||||
- `expect(page).toHaveURL(url)` now supports `setIgnoreCase` [option](./api/class-pageassertions#page-assertions-to-have-url-option-ignore-case).
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 125.0.6422.14
|
||||
* Mozilla Firefox 125.0.1
|
||||
* WebKit 17.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 124
|
||||
* Microsoft Edge 124
|
||||
|
||||
## Version 1.43
|
||||
|
||||
### New APIs
|
||||
|
|
|
|||
|
|
@ -4,6 +4,60 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.44
|
||||
|
||||
### New APIs
|
||||
|
||||
**Accessibility assertions**
|
||||
|
||||
- [`method: LocatorAssertions.toHaveAccessibleName`] checks if the element has the specified accessible name:
|
||||
```python
|
||||
locator = page.get_by_role("button")
|
||||
expect(locator).to_have_accessible_name("Submit")
|
||||
```
|
||||
|
||||
- [`method: LocatorAssertions.toHaveAccessibleDescription`] checks if the element has the specified accessible description:
|
||||
```python
|
||||
locator = page.get_by_role("button")
|
||||
expect(locator).to_have_accessible_description("Upload a photo")
|
||||
```
|
||||
|
||||
- [`method: LocatorAssertions.toHaveRole`] checks if the element has the specified ARIA role:
|
||||
```python
|
||||
locator = page.get_by_test_id("save-button")
|
||||
expect(locator).to_have_role("button")
|
||||
```
|
||||
|
||||
**Locator handler**
|
||||
|
||||
- After executing the handler added with [`method: Page.addLocatorHandler`], Playwright will now wait until the overlay that triggered the handler is not visible anymore. You can opt-out of this behavior with the new `no_wait_after` option.
|
||||
- You can use new `times` option in [`method: Page.addLocatorHandler`] to specify maximum number of times the handler should be run.
|
||||
- The handler in [`method: Page.addLocatorHandler`] now accepts the locator as argument.
|
||||
- New [`method: Page.removeLocatorHandler`] method for removing previously added locator handlers.
|
||||
|
||||
```python
|
||||
locator = page.get_by_text("This interstitial covers the button")
|
||||
page.add_locator_handler(locator, lambda overlay: overlay.locator("#close").click(), times=3, no_wait_after=True)
|
||||
# Run your tests that can be interrupted by the overlay.
|
||||
# ...
|
||||
page.remove_locator_handler(locator)
|
||||
```
|
||||
|
||||
**Miscellaneous options**
|
||||
|
||||
- [`method: PageAssertions.toHaveURL`] now supports `ignore_case` [option](./api/class-pageassertions#page-assertions-to-have-url-option-ignore-case).
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 125.0.6422.14
|
||||
* Mozilla Firefox 125.0.1
|
||||
* WebKit 17.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 124
|
||||
* Microsoft Edge 124
|
||||
|
||||
## Version 1.43
|
||||
|
||||
### New APIs
|
||||
|
|
|
|||
|
|
@ -83,6 +83,15 @@ To run a test with a specific title, use the `-g` flag followed by the title of
|
|||
npx playwright test -g "add a todo item"
|
||||
```
|
||||
|
||||
### Run last failed tests
|
||||
|
||||
To run only the tests that failed in the last test run, first run your tests and then run them again with the `--last-failed` flag.
|
||||
|
||||
```bash
|
||||
npx playwright test --last-failed
|
||||
```
|
||||
|
||||
|
||||
### Run tests in VS Code
|
||||
|
||||
Tests can be run right from VS Code using the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright). Once installed you can simply click the green triangle next to the test you want to run or run all tests from the testing sidebar. Check out our [Getting Started with VS Code](./getting-started-vscode.md#running-tests) guide for more details.
|
||||
|
|
|
|||
|
|
@ -71,7 +71,8 @@ import { test, expect } from '@playwright/test';
|
|||
test('basic test', {
|
||||
annotation: {
|
||||
type: 'issue',
|
||||
description: 'https://github.com/microsoft/playwright/issues/23180',
|
||||
description: 'feature tags API',
|
||||
url: 'https://github.com/microsoft/playwright/issues/23180'
|
||||
},
|
||||
}, async ({ page }) => {
|
||||
await page.goto('https://playwright.dev/');
|
||||
|
|
@ -97,7 +98,8 @@ Test title.
|
|||
- `tag` ?<[string]|[Array]<[string]>>
|
||||
- `annotation` ?<[Object]|[Array]<[Object]>>
|
||||
- `type` <[string]> Annotation type, for example `'issue'`.
|
||||
- `description` ?<[string]> Optional annotation description, for example an issue url.
|
||||
- `description` ?<[string]> Optional annotation description.
|
||||
- `url` ?<[string]> Optional for example an issue url.
|
||||
|
||||
Additional test details.
|
||||
|
||||
|
|
@ -440,6 +442,7 @@ Group title.
|
|||
- `annotation` ?<[Object]|[Array]<[Object]>>
|
||||
- `type` <[string]>
|
||||
- `description` ?<[string]>
|
||||
- `url` ?<[string]>
|
||||
|
||||
Additional details for all tests in the group.
|
||||
|
||||
|
|
@ -568,6 +571,7 @@ Group title.
|
|||
- `annotation` ?<[Object]|[Array]<[Object]>>
|
||||
- `type` <[string]>
|
||||
- `description` ?<[string]>
|
||||
- `url` ?<[string]>
|
||||
|
||||
See [`method: Test.describe`] for details description.
|
||||
|
||||
|
|
@ -623,6 +627,7 @@ Group title.
|
|||
- `annotation` ?<[Object]|[Array]<[Object]>>
|
||||
- `type` <[string]>
|
||||
- `description` ?<[string]>
|
||||
- `url` ?<[string]>
|
||||
|
||||
See [`method: Test.describe`] for details description.
|
||||
|
||||
|
|
@ -676,6 +681,7 @@ Group title.
|
|||
- `annotation` ?<[Object]|[Array]<[Object]>>
|
||||
- `type` <[string]>
|
||||
- `description` ?<[string]>
|
||||
- `url` ?<[string]>
|
||||
|
||||
See [`method: Test.describe`] for details description.
|
||||
|
||||
|
|
@ -727,6 +733,7 @@ Group title.
|
|||
- `annotation` ?<[Object]|[Array]<[Object]>>
|
||||
- `type` <[string]>
|
||||
- `description` ?<[string]>
|
||||
- `url` ?<[string]>
|
||||
|
||||
See [`method: Test.describe`] for details description.
|
||||
|
||||
|
|
@ -782,6 +789,7 @@ Group title.
|
|||
- `annotation` ?<[Object]|[Array]<[Object]>>
|
||||
- `type` <[string]>
|
||||
- `description` ?<[string]>
|
||||
- `url` ?<[string]>
|
||||
|
||||
See [`method: Test.describe`] for details description.
|
||||
|
||||
|
|
@ -839,6 +847,7 @@ Group title.
|
|||
- `annotation` ?<[Object]|[Array]<[Object]>>
|
||||
- `type` <[string]>
|
||||
- `description` ?<[string]>
|
||||
- `url` ?<[string]>
|
||||
|
||||
See [`method: Test.describe`] for details description.
|
||||
|
||||
|
|
@ -891,6 +900,7 @@ Group title.
|
|||
- `annotation` ?<[Object]|[Array]<[Object]>>
|
||||
- `type` <[string]>
|
||||
- `description` ?<[string]>
|
||||
- `url` ?<[string]>
|
||||
|
||||
See [`method: Test.describe`] for details description.
|
||||
|
||||
|
|
@ -1109,6 +1119,7 @@ Test title.
|
|||
- `annotation` ?<[Object]|[Array]<[Object]>>
|
||||
- `type` <[string]>
|
||||
- `description` ?<[string]>
|
||||
- `url` ?<[string]>
|
||||
|
||||
See [`method: Test.(call)`] for test details description.
|
||||
|
||||
|
|
@ -1214,6 +1225,7 @@ Test title.
|
|||
- `annotation` ?<[Object]|[Array]<[Object]>>
|
||||
- `type` <[string]>
|
||||
- `description` ?<[string]>
|
||||
- `url` ?<[string]>
|
||||
|
||||
See [`method: Test.(call)`] for test details description.
|
||||
|
||||
|
|
@ -1291,6 +1303,7 @@ Test title.
|
|||
- `annotation` ?<[Object]|[Array]<[Object]>>
|
||||
- `type` <[string]>
|
||||
- `description` ?<[string]>
|
||||
- `url` ?<[string]>
|
||||
|
||||
See [`method: Test.(call)`] for test details description.
|
||||
|
||||
|
|
@ -1436,6 +1449,7 @@ Test title.
|
|||
- `annotation` ?<[Object]|[Array]<[Object]>>
|
||||
- `type` <[string]>
|
||||
- `description` ?<[string]>
|
||||
- `url` ?<[string]>
|
||||
|
||||
See [`method: Test.(call)`] for test details description.
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ Complete set of Playwright Test options is available in the [configuration file]
|
|||
| Non-option arguments | Each argument is treated as a regular expression matched against the full test file path. Only tests from the files matching the pattern will be executed. Special symbols like `$` or `*` should be escaped with `\`. In many shells/terminals you may need to quote the arguments. |
|
||||
| `-c <file>` or `--config <file>`| Configuration file. If not passed, defaults to `playwright.config.ts` or `playwright.config.js` in the current directory. |
|
||||
| `--debug`| Run tests with Playwright Inspector. Shortcut for `PWDEBUG=1` environment variable and `--timeout=0 --max-failures=1 --headed --workers=1` options.|
|
||||
| `--fail-on-flaky-tests` | Fails test runs that contain flaky tests. By default flaky tests count as successes. |
|
||||
| `--forbid-only` | Whether to disallow `test.only`. Useful on CI.|
|
||||
| `--global-timeout <number>` | Total timeout for the whole test run in milliseconds. By default, there is no global timeout. Learn more about [various timeouts](./test-timeouts.md).|
|
||||
| `-g <grep>` or `--grep <grep>` | Only run tests matching this regular expression. For example, this will run `'should add to cart'` when passed `-g "add to cart"`. The regular expression will be tested against the string that consists of the test file name, `test.describe` titles if any, test title and all test tags, separated by spaces, e.g. `my-test.spec.ts my-suite my-test @smoke`. The filter does not apply to the tests from dependency projects, i.e. Playwright will still run all tests from [project dependencies](./test-projects.md#dependencies). |
|
||||
|
|
|
|||
|
|
@ -97,6 +97,15 @@ export default defineConfig({
|
|||
});
|
||||
```
|
||||
|
||||
List report supports the following configuration options and environment variables:
|
||||
|
||||
| Environment Variable Name | Reporter Config Option| Description | Default
|
||||
|---|---|---|---|
|
||||
| `PLAYWRIGHT_LIST_PRINT_STEPS` | `printSteps` | Whether to print each step on its own line. | `false`
|
||||
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. | `true` when terminal is in TTY mode, `false` otherwise.
|
||||
| `FORCE_COLOR` | | Whether to produce colored output. | `true` when terminal is in TTY mode, `false` otherwise.
|
||||
|
||||
|
||||
### Line reporter
|
||||
|
||||
Line reporter is more concise than the list reporter. It uses a single line to report last finished test, and prints failures when they occur. Line reporter is useful for large test suites where it shows the progress but does not spam the output by listing all the tests.
|
||||
|
|
@ -127,6 +136,14 @@ Running 124 tests using 6 workers
|
|||
[23/124] gitignore.spec.ts - should respect nested .gitignore
|
||||
```
|
||||
|
||||
Line report supports the following configuration options and environment variables:
|
||||
|
||||
| Environment Variable Name | Reporter Config Option| Description | Default
|
||||
|---|---|---|---|
|
||||
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. | `true` when terminal is in TTY mode, `false` otherwise.
|
||||
| `FORCE_COLOR` | | Whether to produce colored output. | `true` when terminal is in TTY mode, `false` otherwise.
|
||||
|
||||
|
||||
### Dot reporter
|
||||
|
||||
Dot reporter is very concise - it only produces a single character per successful test run. It is the default on CI and useful where you don't want a lot of output.
|
||||
|
|
@ -150,6 +167,24 @@ Running 124 tests using 6 workers
|
|||
······F·············································
|
||||
```
|
||||
|
||||
One character is displayed for each test that has run, indicating its status:
|
||||
|
||||
| Character | Description
|
||||
|---|---|
|
||||
| `·` | Passed
|
||||
| `F` | Failed
|
||||
| `×` | Failed or timed out - and will be retried
|
||||
| `±` | Passed on retry (flaky)
|
||||
| `T` | Timed out
|
||||
| `°` | Skipped
|
||||
|
||||
Dot report supports the following configuration options and environment variables:
|
||||
|
||||
| Environment Variable Name | Reporter Config Option| Description | Default
|
||||
|---|---|---|---|
|
||||
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. | `true` when terminal is in TTY mode, `false` otherwise.
|
||||
| `FORCE_COLOR` | | Whether to produce colored output. | `true` when terminal is in TTY mode, `false` otherwise.
|
||||
|
||||
### HTML reporter
|
||||
|
||||
HTML reporter produces a self-contained folder that contains report for the test run that can be served as a web page.
|
||||
|
|
@ -159,7 +194,7 @@ npx playwright test --reporter=html
|
|||
```
|
||||
|
||||
By default, HTML report is opened automatically if some of the tests failed. You can control this behavior via the
|
||||
`open` property in the Playwright config or the `PW_TEST_HTML_REPORT_OPEN` environmental variable. The possible values for that property are `always`, `never` and `on-failure`
|
||||
`open` property in the Playwright config or the `PLAYWRIGHT_HTML_OPEN` environmental variable. The possible values for that property are `always`, `never` and `on-failure`
|
||||
(default).
|
||||
|
||||
You can also configure `host` and `port` that are used to serve the HTML report.
|
||||
|
|
@ -173,7 +208,7 @@ export default defineConfig({
|
|||
```
|
||||
|
||||
By default, report is written into the `playwright-report` folder in the current working directory. One can override
|
||||
that location using the `PLAYWRIGHT_HTML_REPORT` environment variable or a reporter configuration.
|
||||
that location using the `PLAYWRIGHT_HTML_OUTPUT_DIR` environment variable or a reporter configuration.
|
||||
|
||||
In configuration file, pass options directly:
|
||||
|
||||
|
|
@ -207,6 +242,16 @@ Or if there is a custom folder name:
|
|||
npx playwright show-report my-report
|
||||
```
|
||||
|
||||
HTML report supports the following configuration options and environment variables:
|
||||
|
||||
| Environment Variable Name | Reporter Config Option| Description | Default
|
||||
|---|---|---|---|
|
||||
| `PLAYWRIGHT_HTML_OUTPUT_DIR` | `outputFolder` | Directory to save the report to. | `playwright-report`
|
||||
| `PLAYWRIGHT_HTML_OPEN` | `open` | When to open the html report in the browser, one of `'always'`, `'never'` or `'on-failure'` | `'on-failure'`
|
||||
| `PLAYWRIGHT_HTML_HOST` | `host` | When report opens in the browser, it will be served bound to this hostname. | `localhost`
|
||||
| `PLAYWRIGHT_HTML_PORT` | `port` | When report opens in the browser, it will be served on this port. | `9323` or any available port when `9323` is not available.
|
||||
| `PLAYWRIGHT_HTML_ATTACHMENTS_BASE_URL` | `attachmentsBaseURL` | A separate location where attachments from the `data` subdirectory are uploaded. Only needed when you upload report and `data` separately to different locations. | `data/`
|
||||
|
||||
### Blob reporter
|
||||
|
||||
Blob reports contain all the details about the test run and can be used later to produce any other report. Their primary function is to facilitate the merging of reports from [sharded tests](./test-sharding.md).
|
||||
|
|
@ -308,8 +353,8 @@ JUnit report supports following configuration options and environment variables:
|
|||
| `PLAYWRIGHT_JUNIT_OUTPUT_DIR` | | Directory to save the output file. Ignored if output file is not specified. | `cwd` or config directory.
|
||||
| `PLAYWRIGHT_JUNIT_OUTPUT_NAME` | `outputFile` | Base file name for the output, relative to the output dir. | JUnit report is printed to the stdout.
|
||||
| `PLAYWRIGHT_JUNIT_OUTPUT_FILE` | `outputFile` | Full path to the output file. If defined, `PLAYWRIGHT_JUNIT_OUTPUT_DIR` and `PLAYWRIGHT_JUNIT_OUTPUT_NAME` will be ignored. | JUnit report is printed to the stdout.
|
||||
| | `stripANSIControlSequences` | Whether to remove ANSI control sequences from the text before writing it in the report. | By default output text is added as is.
|
||||
| | `includeProjectInTestName` | Whether to include Playwright project name in every test case as a name prefix. | By default not included.
|
||||
| `PLAYWRIGHT_JUNIT_STRIP_ANSI` | `stripANSIControlSequences` | Whether to remove ANSI control sequences from the text before writing it in the report. | By default output text is added as is.
|
||||
| `PLAYWRIGHT_JUNIT_INCLUDE_PROJECT_IN_TEST_NAME` | `includeProjectInTestName` | Whether to include Playwright project name in every test case as a name prefix. | By default not included.
|
||||
| `PLAYWRIGHT_JUNIT_SUITE_ID` | | Value of the `id` attribute on the root `<testsuites/>` report entry. | Empty string.
|
||||
| `PLAYWRIGHT_JUNIT_SUITE_NAME` | | Value of the `name` attribute on the root `<testsuites/>` report entry. | Empty string.
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,58 @@ test.describe('New Todo', () => {
|
|||
await checkNumberOfTodosInLocalStorage(page, 1);
|
||||
});
|
||||
|
||||
test('should clear text input field when an item is added 3', async ({ page }) => {
|
||||
// create a new todo locator
|
||||
const newTodo = page.getByPlaceholder('What needs to be done?');
|
||||
|
||||
// Create one todo item.
|
||||
await newTodo.fill(TODO_ITEMS[0]);
|
||||
await newTodo.press('Enter');
|
||||
|
||||
// Check that input is empty.
|
||||
await expect(newTodo).toBeEmpty();
|
||||
await checkNumberOfTodosInLocalStorage(page, 1);
|
||||
});
|
||||
|
||||
test('should clear text input field when an item is added 4', async ({ page }) => {
|
||||
// create a new todo locator
|
||||
const newTodo = page.getByPlaceholder('What needs to be done?');
|
||||
|
||||
// Create one todo item.
|
||||
await newTodo.fill(TODO_ITEMS[0]);
|
||||
await newTodo.press('Enter');
|
||||
|
||||
// Check that input is empty.
|
||||
await expect(newTodo).toBeEmpty();
|
||||
await checkNumberOfTodosInLocalStorage(page, 1);
|
||||
});
|
||||
|
||||
test('should clear text input field when an item is added 5', async ({ page }) => {
|
||||
// create a new todo locator
|
||||
const newTodo = page.getByPlaceholder('What needs to be done?');
|
||||
|
||||
// Create one todo item.
|
||||
await newTodo.fill(TODO_ITEMS[0]);
|
||||
await newTodo.press('Enter');
|
||||
|
||||
// Check that input is empty.
|
||||
await expect(newTodo).toBeEmpty();
|
||||
await checkNumberOfTodosInLocalStorage(page, 1);
|
||||
});
|
||||
|
||||
test('should clear text input field when an item is added 2', async ({ page }) => {
|
||||
// create a new todo locator
|
||||
const newTodo = page.getByPlaceholder('What needs to be done?');
|
||||
|
||||
// Create one todo item.
|
||||
await newTodo.fill(TODO_ITEMS[0]);
|
||||
await newTodo.press('Enter');
|
||||
|
||||
// Check that input is empty.
|
||||
await expect(newTodo).toBeEmpty();
|
||||
await checkNumberOfTodosInLocalStorage(page, 1);
|
||||
});
|
||||
|
||||
test('should append new items to the bottom of the list', async ({ page }) => {
|
||||
// Create 3 items.
|
||||
await createDefaultTodos(page);
|
||||
|
|
@ -351,6 +403,22 @@ test.describe('Routing', () => {
|
|||
await expect(page.getByTestId('todo-item')).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
|
||||
});
|
||||
|
||||
test('should allow me to display active items 2', async ({ page }) => {
|
||||
await page.locator('.todo-list li .toggle').nth(1).check();
|
||||
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
|
||||
await page.getByRole('link', { name: 'Active' }).click();
|
||||
await expect(page.getByTestId('todo-item')).toHaveCount(2);
|
||||
await expect(page.getByTestId('todo-item')).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
|
||||
});
|
||||
|
||||
test('should allow me to display active items 3', async ({ page }) => {
|
||||
await page.locator('.todo-list li .toggle').nth(1).check();
|
||||
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
|
||||
await page.getByRole('link', { name: 'Active' }).click();
|
||||
await expect(page.getByTestId('todo-item')).toHaveCount(2);
|
||||
await expect(page.getByTestId('todo-item')).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]);
|
||||
});
|
||||
|
||||
test('should respect the back button', async ({ page }) => {
|
||||
await page.locator('.todo-list li .toggle').nth(1).check();
|
||||
await checkNumberOfCompletedTodosInLocalStorage(page, 1);
|
||||
|
|
@ -429,3 +497,4 @@ async function checkTodosInLocalStorage(page: Page, title: string) {
|
|||
return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t);
|
||||
}, title);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@ const testCase: TestCase = {
|
|||
projectName: 'chromium',
|
||||
location: { file: 'test.spec.ts', line: 42, column: 0 },
|
||||
annotations: [
|
||||
{ type: 'annotation', description: 'Annotation text' },
|
||||
{ type: 'annotation', description: 'Another annotation text' },
|
||||
{ type: 'annotation', description: 'Annotation text', url: 'example url' },
|
||||
{ type: 'annotation', description: 'Another annotation text', url: 'Another example url' },
|
||||
],
|
||||
tags: [],
|
||||
outcome: 'expected',
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export type TestFileSummary = {
|
|||
stats: Stats;
|
||||
};
|
||||
|
||||
export type TestCaseAnnotation = { type: string, description?: string };
|
||||
export type TestCaseAnnotation = { type: string, description?: string, url?: string};
|
||||
|
||||
export type TestCaseSummary = {
|
||||
testId: string,
|
||||
|
|
|
|||
|
|
@ -3,37 +3,39 @@
|
|||
"browsers": [
|
||||
{
|
||||
"name": "chromium",
|
||||
"revision": "1118",
|
||||
"revision": "1119",
|
||||
"installByDefault": true,
|
||||
"browserVersion": "125.0.6422.41"
|
||||
"browserVersion": "126.0.6478.8"
|
||||
},
|
||||
{
|
||||
"name": "chromium-tip-of-tree",
|
||||
"revision": "1221",
|
||||
"revision": "1222",
|
||||
"installByDefault": false,
|
||||
"browserVersion": "126.0.6478.0"
|
||||
"browserVersion": "126.0.6478.8"
|
||||
},
|
||||
{
|
||||
"name": "firefox",
|
||||
"revision": "1449",
|
||||
"revision": "1450",
|
||||
"installByDefault": true,
|
||||
"browserVersion": "125.0.1"
|
||||
"browserVersion": "126.0"
|
||||
},
|
||||
{
|
||||
"name": "firefox-beta",
|
||||
"revision": "1449",
|
||||
"revision": "1450",
|
||||
"installByDefault": false,
|
||||
"browserVersion": "126.0b1"
|
||||
"browserVersion": "127.0b3"
|
||||
},
|
||||
{
|
||||
"name": "webkit",
|
||||
"revision": "2007",
|
||||
"revision": "2010",
|
||||
"installByDefault": true,
|
||||
"revisionOverrides": {
|
||||
"mac10.14": "1446",
|
||||
"mac10.15": "1616",
|
||||
"mac11": "1816",
|
||||
"mac11-arm64": "1816"
|
||||
"mac11-arm64": "1816",
|
||||
"mac12": "2009",
|
||||
"mac12-arm64": "2009"
|
||||
},
|
||||
"browserVersion": "17.4"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -445,6 +445,9 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
|||
return;
|
||||
this._closeReason = options.reason;
|
||||
this._closeWasCalled = true;
|
||||
await this._wrapApiCall(async () => {
|
||||
await this.request.dispose(options);
|
||||
}, true);
|
||||
await this._wrapApiCall(async () => {
|
||||
await this._browserType?._willCloseContext(this);
|
||||
for (const [harId, harParams] of this._harRecorders) {
|
||||
|
|
|
|||
|
|
@ -103,7 +103,13 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
|
|||
async dispose(options: { reason?: string } = {}): Promise<void> {
|
||||
this._closeReason = options.reason;
|
||||
await this._instrumentation.onWillCloseRequestContext(this);
|
||||
await this._channel.dispose(options);
|
||||
try {
|
||||
await this._channel.dispose(options);
|
||||
} catch (e) {
|
||||
if (isTargetClosedError(e))
|
||||
return;
|
||||
throw e;
|
||||
}
|
||||
this._tracing._resetStackCounter();
|
||||
this._request?._contexts.delete(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,6 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap
|
|||
if (this._redirectedFrom)
|
||||
this._redirectedFrom._redirectedTo = this;
|
||||
this._provisionalHeaders = new RawHeaders(initializer.headers);
|
||||
this._fallbackOverrides.postDataBuffer = initializer.postData;
|
||||
this._timing = {
|
||||
startTime: 0,
|
||||
domainLookupStart: -1,
|
||||
|
|
@ -128,11 +127,11 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap
|
|||
}
|
||||
|
||||
postData(): string | null {
|
||||
return this._fallbackOverrides.postDataBuffer?.toString('utf-8') || null;
|
||||
return (this._fallbackOverrides.postDataBuffer || this._initializer.postData)?.toString('utf-8') || null;
|
||||
}
|
||||
|
||||
postDataBuffer(): Buffer | null {
|
||||
return this._fallbackOverrides.postDataBuffer || null;
|
||||
return this._fallbackOverrides.postDataBuffer || this._initializer.postData || null;
|
||||
}
|
||||
|
||||
postDataJSON(): Object | null {
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ export class CRNetworkManager {
|
|||
requestPausedSessionInfo!.session._sendMayFail('Fetch.fulfillRequest', {
|
||||
requestId: requestPausedEvent.requestId,
|
||||
responseCode: 204,
|
||||
responsePhrase: network.STATUS_TEXTS['204'],
|
||||
responsePhrase: network.statusText(204),
|
||||
responseHeaders,
|
||||
body: '',
|
||||
});
|
||||
|
|
@ -376,6 +376,10 @@ export class CRNetworkManager {
|
|||
if (response.body || !expectedLength)
|
||||
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
||||
|
||||
// Make sure no network requests sent while reading the body for fulfilled requests.
|
||||
if (request._route?._fulfilled)
|
||||
return Buffer.from('');
|
||||
|
||||
// For <link prefetch we are going to receive empty body with non-empty content-length expectation. Reach out for the actual content.
|
||||
const resource = await session.send('Network.loadNetworkResource', { url: request.request.url(), frameId: this._serviceWorker ? undefined : request.request.frame()!._id, options: { disableCache: false, includeCredentials: true } });
|
||||
const chunks: Buffer[] = [];
|
||||
|
|
@ -595,6 +599,7 @@ class RouteImpl implements network.RouteDelegate {
|
|||
private readonly _session: CRSession;
|
||||
private _interceptionId: string;
|
||||
_alreadyContinuedParams: Protocol.Fetch.continueRequestParameters | undefined;
|
||||
_fulfilled: boolean = false;
|
||||
|
||||
constructor(session: CRSession, interceptionId: string) {
|
||||
this._session = session;
|
||||
|
|
@ -615,6 +620,7 @@ class RouteImpl implements network.RouteDelegate {
|
|||
}
|
||||
|
||||
async fulfill(response: types.NormalizedFulfillResponse) {
|
||||
this._fulfilled = true;
|
||||
const body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64');
|
||||
|
||||
const responseHeaders = splitSetCookieHeader(response.headers);
|
||||
|
|
@ -622,7 +628,7 @@ class RouteImpl implements network.RouteDelegate {
|
|||
await this._session.send('Fetch.fulfillRequest', {
|
||||
requestId: this._interceptionId!,
|
||||
responseCode: response.status,
|
||||
responsePhrase: network.STATUS_TEXTS[String(response.status)],
|
||||
responsePhrase: network.statusText(response.status),
|
||||
responseHeaders,
|
||||
body,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -841,6 +841,7 @@ CORS RFC1918 enforcement.
|
|||
clientSecurityState?: Network.ClientSecurityState;
|
||||
}
|
||||
export type AttributionReportingIssueType = "PermissionPolicyDisabled"|"UntrustworthyReportingOrigin"|"InsecureContext"|"InvalidHeader"|"InvalidRegisterTriggerHeader"|"SourceAndTriggerHeaders"|"SourceIgnored"|"TriggerIgnored"|"OsSourceIgnored"|"OsTriggerIgnored"|"InvalidRegisterOsSourceHeader"|"InvalidRegisterOsTriggerHeader"|"WebAndOsHeaders"|"NoWebOrOsSupport"|"NavigationRegistrationWithoutTransientUserActivation"|"InvalidInfoHeader"|"NoRegisterSourceHeader"|"NoRegisterTriggerHeader"|"NoRegisterOsSourceHeader"|"NoRegisterOsTriggerHeader";
|
||||
export type SharedDictionaryError = "UseErrorCrossOriginNoCorsRequest"|"UseErrorDictionaryLoadFailure"|"UseErrorMatchingDictionaryNotUsed"|"UseErrorUnexpectedContentDictionaryHeader"|"WriteErrorCossOriginNoCorsRequest"|"WriteErrorDisallowedBySettings"|"WriteErrorExpiredResponse"|"WriteErrorFeatureDisabled"|"WriteErrorInsufficientResources"|"WriteErrorInvalidMatchField"|"WriteErrorInvalidStructuredHeader"|"WriteErrorNavigationRequest"|"WriteErrorNoMatchField"|"WriteErrorNonListMatchDestField"|"WriteErrorNonSecureContext"|"WriteErrorNonStringIdField"|"WriteErrorNonStringInMatchDestList"|"WriteErrorNonStringMatchField"|"WriteErrorNonTokenTypeField"|"WriteErrorRequestAborted"|"WriteErrorShuttingDown"|"WriteErrorTooLongIdField"|"WriteErrorUnsupportedType";
|
||||
/**
|
||||
* Details for issues around "Attribution Reporting API" usage.
|
||||
Explainer: https://github.com/WICG/attribution-reporting-api
|
||||
|
|
@ -870,6 +871,10 @@ instead of "limited-quirks".
|
|||
url: string;
|
||||
location?: SourceCodeLocation;
|
||||
}
|
||||
export interface SharedDictionaryIssueDetails {
|
||||
sharedDictionaryError: SharedDictionaryError;
|
||||
request: AffectedRequest;
|
||||
}
|
||||
export type GenericIssueErrorType = "CrossOriginPortalPostMessageError"|"FormLabelForNameError"|"FormDuplicateIdForInputError"|"FormInputWithNoLabelError"|"FormAutocompleteAttributeEmptyError"|"FormEmptyIdAndNameAttributesForInputError"|"FormAriaLabelledByToNonExistingId"|"FormInputAssignedAutocompleteValueToIdOrNameAttributeError"|"FormLabelHasNeitherForNorNestedInput"|"FormLabelForMatchesNonExistingIdError"|"FormInputHasWrongButWellIntendedAutocompleteValueError"|"ResponseWasBlockedByORB";
|
||||
/**
|
||||
* Depending on the concrete errorType, different properties are set.
|
||||
|
|
@ -997,7 +1002,7 @@ registrations being ignored.
|
|||
optional fields in InspectorIssueDetails to convey more specific
|
||||
information about the kind of issue.
|
||||
*/
|
||||
export type InspectorIssueCode = "CookieIssue"|"MixedContentIssue"|"BlockedByResponseIssue"|"HeavyAdIssue"|"ContentSecurityPolicyIssue"|"SharedArrayBufferIssue"|"LowTextContrastIssue"|"CorsIssue"|"AttributionReportingIssue"|"QuirksModeIssue"|"NavigatorUserAgentIssue"|"GenericIssue"|"DeprecationIssue"|"ClientHintIssue"|"FederatedAuthRequestIssue"|"BounceTrackingIssue"|"CookieDeprecationMetadataIssue"|"StylesheetLoadingIssue"|"FederatedAuthUserInfoRequestIssue"|"PropertyRuleIssue";
|
||||
export type InspectorIssueCode = "CookieIssue"|"MixedContentIssue"|"BlockedByResponseIssue"|"HeavyAdIssue"|"ContentSecurityPolicyIssue"|"SharedArrayBufferIssue"|"LowTextContrastIssue"|"CorsIssue"|"AttributionReportingIssue"|"QuirksModeIssue"|"NavigatorUserAgentIssue"|"GenericIssue"|"DeprecationIssue"|"ClientHintIssue"|"FederatedAuthRequestIssue"|"BounceTrackingIssue"|"CookieDeprecationMetadataIssue"|"StylesheetLoadingIssue"|"FederatedAuthUserInfoRequestIssue"|"PropertyRuleIssue"|"SharedDictionaryIssue";
|
||||
/**
|
||||
* This struct holds a list of optional fields with additional information
|
||||
specific to the kind of issue. When adding a new issue code, please also
|
||||
|
|
@ -1024,6 +1029,7 @@ add a new optional field to this type.
|
|||
stylesheetLoadingIssueDetails?: StylesheetLoadingIssueDetails;
|
||||
propertyRuleIssueDetails?: PropertyRuleIssueDetails;
|
||||
federatedAuthUserInfoRequestIssueDetails?: FederatedAuthUserInfoRequestIssueDetails;
|
||||
sharedDictionaryIssueDetails?: SharedDictionaryIssueDetails;
|
||||
}
|
||||
/**
|
||||
* A unique id for a DevTools inspector issue. Allows other entities (e.g.
|
||||
|
|
@ -1121,6 +1127,33 @@ using Audits.issueAdded event.
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines commands and events for browser extensions. Available if the client
|
||||
is connected using the --remote-debugging-pipe flag and
|
||||
the --enable-unsafe-extension-debugging flag is set.
|
||||
*/
|
||||
export module Extensions {
|
||||
|
||||
|
||||
/**
|
||||
* Installs an unpacked extension from the filesystem similar to
|
||||
--load-extension CLI flags. Returns extension ID once the extension
|
||||
has been installed.
|
||||
*/
|
||||
export type loadUnpackedParameters = {
|
||||
/**
|
||||
* Absolute file path.
|
||||
*/
|
||||
path: string;
|
||||
}
|
||||
export type loadUnpackedReturnValue = {
|
||||
/**
|
||||
* Extension id.
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines commands and events for Autofill.
|
||||
*/
|
||||
|
|
@ -3450,7 +3483,7 @@ front-end.
|
|||
/**
|
||||
* Pseudo element type.
|
||||
*/
|
||||
export type PseudoType = "first-line"|"first-letter"|"before"|"after"|"marker"|"backdrop"|"selection"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-markers"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new";
|
||||
export type PseudoType = "first-line"|"first-letter"|"before"|"after"|"marker"|"backdrop"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-markers"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new";
|
||||
/**
|
||||
* Shadow root type.
|
||||
*/
|
||||
|
|
@ -4426,6 +4459,25 @@ appear on top of all other content.
|
|||
*/
|
||||
nodeIds: NodeId[];
|
||||
}
|
||||
/**
|
||||
* Returns the NodeId of the matched element according to certain relations.
|
||||
*/
|
||||
export type getElementByRelationParameters = {
|
||||
/**
|
||||
* Id of the node from which to query the relation.
|
||||
*/
|
||||
nodeId: NodeId;
|
||||
/**
|
||||
* Type of relation to get.
|
||||
*/
|
||||
relation: "PopoverTarget";
|
||||
}
|
||||
export type getElementByRelationReturnValue = {
|
||||
/**
|
||||
* NodeId of the element matching the queried relation.
|
||||
*/
|
||||
nodeId: NodeId;
|
||||
}
|
||||
/**
|
||||
* Re-does the last undone action.
|
||||
*/
|
||||
|
|
@ -8309,8 +8361,16 @@ records.
|
|||
*/
|
||||
export type ServiceWorkerRouterSource = "network"|"cache"|"fetch-event"|"race-network-and-fetch-handler";
|
||||
export interface ServiceWorkerRouterInfo {
|
||||
ruleIdMatched: number;
|
||||
matchedSourceType: ServiceWorkerRouterSource;
|
||||
/**
|
||||
* ID of the rule matched. If there is a matched rule, this field will
|
||||
be set, otherwiser no value will be set.
|
||||
*/
|
||||
ruleIdMatched?: number;
|
||||
/**
|
||||
* The router source of the matched rule. If there is a matched rule, this
|
||||
field will be set, otherwise no value will be set.
|
||||
*/
|
||||
matchedSourceType?: ServiceWorkerRouterSource;
|
||||
}
|
||||
/**
|
||||
* HTTP response data.
|
||||
|
|
@ -8385,7 +8445,10 @@ records.
|
|||
*/
|
||||
fromEarlyHints?: boolean;
|
||||
/**
|
||||
* Information about how Service Worker Static Router was used.
|
||||
* Information about how ServiceWorker Static Router API was used. If this
|
||||
field is set with `matchedSourceType` field, a matching rule is found.
|
||||
If this field is set without `matchedSource`, no matching rule is found.
|
||||
Otherwise, the API is not used.
|
||||
*/
|
||||
serviceWorkerRouterInfo?: ServiceWorkerRouterInfo;
|
||||
/**
|
||||
|
|
@ -11167,7 +11230,7 @@ as an ad.
|
|||
* All Permissions Policy features. This enum should match the one defined
|
||||
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"|"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"|"window-placement"|"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"|"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.
|
||||
*/
|
||||
|
|
@ -11755,7 +11818,7 @@ https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-expl
|
|||
/**
|
||||
* 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"|"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";
|
||||
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"|"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";
|
||||
/**
|
||||
* Types of not restored reasons for back-forward cache.
|
||||
*/
|
||||
|
|
@ -12334,7 +12397,7 @@ option, use with caution.
|
|||
This API always waits for the manifest to be loaded.
|
||||
If manifestId is provided, and it does not match the manifest of the
|
||||
current document, this API errors out.
|
||||
If there isn’t a loaded page, this API errors out immediately.
|
||||
If there is not a loaded page, this API errors out immediately.
|
||||
*/
|
||||
export type getAppManifestParameters = {
|
||||
manifestId?: string;
|
||||
|
|
@ -16690,7 +16753,7 @@ possible for multiple rule sets and links to trigger a single attempt.
|
|||
/**
|
||||
* 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";
|
||||
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";
|
||||
/**
|
||||
* Preloading status values, see also PreloadingTriggeringOutcome. This
|
||||
status is shared by prefetchStatusUpdated and prerenderStatusUpdated.
|
||||
|
|
@ -16921,6 +16984,52 @@ https://web.dev/learn/pwa/web-app-manifest.
|
|||
badgeCount: number;
|
||||
fileHandlers: FileHandler[];
|
||||
}
|
||||
/**
|
||||
* Installs the given manifest identity, optionally using the given install_url
|
||||
or IWA bundle location.
|
||||
|
||||
TODO(crbug.com/337872319) Support IWA to meet the following specific
|
||||
requirement.
|
||||
IWA-specific install description: If the manifest_id is isolated-app://,
|
||||
install_url_or_bundle_url is required, and can be either an http(s) URL or
|
||||
file:// URL pointing to a signed web bundle (.swbn). The .swbn file's
|
||||
signing key must correspond to manifest_id. If Chrome is not in IWA dev
|
||||
mode, the installation will fail, regardless of the state of the allowlist.
|
||||
*/
|
||||
export type installParameters = {
|
||||
manifestId: string;
|
||||
/**
|
||||
* The location of the app or bundle overriding the one derived from the
|
||||
manifestId.
|
||||
*/
|
||||
installUrlOrBundleUrl?: string;
|
||||
}
|
||||
export type installReturnValue = {
|
||||
}
|
||||
/**
|
||||
* Uninstals the given manifest_id and closes any opened app windows.
|
||||
*/
|
||||
export type uninstallParameters = {
|
||||
manifestId: string;
|
||||
}
|
||||
export type uninstallReturnValue = {
|
||||
}
|
||||
/**
|
||||
* Launches the installed web app, or an url in the same web app instead of the
|
||||
default start url if it is provided. Returns a tab / web contents based
|
||||
Target.TargetID which can be used to attach to via Target.attachToTarget or
|
||||
similar APIs.
|
||||
*/
|
||||
export type launchParameters = {
|
||||
manifestId: string;
|
||||
url?: string;
|
||||
}
|
||||
export type launchReturnValue = {
|
||||
/**
|
||||
* ID of the tab target created as a result.
|
||||
*/
|
||||
targetId: Target.TargetID;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -19773,6 +19882,7 @@ Error was thrown.
|
|||
"Audits.enable": Audits.enableParameters;
|
||||
"Audits.checkContrast": Audits.checkContrastParameters;
|
||||
"Audits.checkFormsIssues": Audits.checkFormsIssuesParameters;
|
||||
"Extensions.loadUnpacked": Extensions.loadUnpackedParameters;
|
||||
"Autofill.trigger": Autofill.triggerParameters;
|
||||
"Autofill.setAddresses": Autofill.setAddressesParameters;
|
||||
"Autofill.disable": Autofill.disableParameters;
|
||||
|
|
@ -19870,6 +19980,7 @@ Error was thrown.
|
|||
"DOM.querySelector": DOM.querySelectorParameters;
|
||||
"DOM.querySelectorAll": DOM.querySelectorAllParameters;
|
||||
"DOM.getTopLayerElements": DOM.getTopLayerElementsParameters;
|
||||
"DOM.getElementByRelation": DOM.getElementByRelationParameters;
|
||||
"DOM.redo": DOM.redoParameters;
|
||||
"DOM.removeAttribute": DOM.removeAttributeParameters;
|
||||
"DOM.removeNode": DOM.removeNodeParameters;
|
||||
|
|
@ -20256,6 +20367,9 @@ Error was thrown.
|
|||
"FedCm.dismissDialog": FedCm.dismissDialogParameters;
|
||||
"FedCm.resetCooldown": FedCm.resetCooldownParameters;
|
||||
"PWA.getOsAppState": PWA.getOsAppStateParameters;
|
||||
"PWA.install": PWA.installParameters;
|
||||
"PWA.uninstall": PWA.uninstallParameters;
|
||||
"PWA.launch": PWA.launchParameters;
|
||||
"Console.clearMessages": Console.clearMessagesParameters;
|
||||
"Console.disable": Console.disableParameters;
|
||||
"Console.enable": Console.enableParameters;
|
||||
|
|
@ -20361,6 +20475,7 @@ Error was thrown.
|
|||
"Audits.enable": Audits.enableReturnValue;
|
||||
"Audits.checkContrast": Audits.checkContrastReturnValue;
|
||||
"Audits.checkFormsIssues": Audits.checkFormsIssuesReturnValue;
|
||||
"Extensions.loadUnpacked": Extensions.loadUnpackedReturnValue;
|
||||
"Autofill.trigger": Autofill.triggerReturnValue;
|
||||
"Autofill.setAddresses": Autofill.setAddressesReturnValue;
|
||||
"Autofill.disable": Autofill.disableReturnValue;
|
||||
|
|
@ -20458,6 +20573,7 @@ Error was thrown.
|
|||
"DOM.querySelector": DOM.querySelectorReturnValue;
|
||||
"DOM.querySelectorAll": DOM.querySelectorAllReturnValue;
|
||||
"DOM.getTopLayerElements": DOM.getTopLayerElementsReturnValue;
|
||||
"DOM.getElementByRelation": DOM.getElementByRelationReturnValue;
|
||||
"DOM.redo": DOM.redoReturnValue;
|
||||
"DOM.removeAttribute": DOM.removeAttributeReturnValue;
|
||||
"DOM.removeNode": DOM.removeNodeReturnValue;
|
||||
|
|
@ -20844,6 +20960,9 @@ Error was thrown.
|
|||
"FedCm.dismissDialog": FedCm.dismissDialogReturnValue;
|
||||
"FedCm.resetCooldown": FedCm.resetCooldownReturnValue;
|
||||
"PWA.getOsAppState": PWA.getOsAppStateReturnValue;
|
||||
"PWA.install": PWA.installReturnValue;
|
||||
"PWA.uninstall": PWA.uninstallReturnValue;
|
||||
"PWA.launch": PWA.launchReturnValue;
|
||||
"Console.clearMessages": Console.clearMessagesReturnValue;
|
||||
"Console.disable": Console.disableReturnValue;
|
||||
"Console.enable": Console.enableReturnValue;
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Galaxy S5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -121,7 +121,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -132,7 +132,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S8": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 740
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S8 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 740,
|
||||
"height": 360
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S9+": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 320,
|
||||
"height": 658
|
||||
|
|
@ -165,7 +165,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S9+ landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 658,
|
||||
"height": 320
|
||||
|
|
@ -176,7 +176,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy Tab S4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 712,
|
||||
"height": 1138
|
||||
|
|
@ -187,7 +187,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy Tab S4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 1138,
|
||||
"height": 712
|
||||
|
|
@ -978,7 +978,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"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/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 384,
|
||||
"height": 640
|
||||
|
|
@ -989,7 +989,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"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/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 384
|
||||
|
|
@ -1000,7 +1000,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"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/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1011,7 +1011,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"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/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1022,7 +1022,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"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/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1033,7 +1033,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"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/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1044,7 +1044,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 10": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 800,
|
||||
"height": 1280
|
||||
|
|
@ -1055,7 +1055,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 10 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
|
|
@ -1066,7 +1066,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 384,
|
||||
"height": 640
|
||||
|
|
@ -1077,7 +1077,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 384
|
||||
|
|
@ -1088,7 +1088,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1099,7 +1099,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1110,7 +1110,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5X": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1121,7 +1121,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"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/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1132,7 +1132,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1143,7 +1143,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1154,7 +1154,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6P": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1165,7 +1165,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"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/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1176,7 +1176,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 7": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 600,
|
||||
"height": 960
|
||||
|
|
@ -1187,7 +1187,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 7 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 960,
|
||||
"height": 600
|
||||
|
|
@ -1242,7 +1242,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Pixel 2": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 411,
|
||||
"height": 731
|
||||
|
|
@ -1253,7 +1253,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 2 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 731,
|
||||
"height": 411
|
||||
|
|
@ -1264,7 +1264,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"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/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 411,
|
||||
"height": 823
|
||||
|
|
@ -1275,7 +1275,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"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/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 823,
|
||||
"height": 411
|
||||
|
|
@ -1286,7 +1286,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 3": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 393,
|
||||
"height": 786
|
||||
|
|
@ -1297,7 +1297,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 3 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 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/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 786,
|
||||
"height": 393
|
||||
|
|
@ -1308,7 +1308,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 353,
|
||||
"height": 745
|
||||
|
|
@ -1319,7 +1319,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 745,
|
||||
"height": 353
|
||||
|
|
@ -1330,7 +1330,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4a (5G)": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 412,
|
||||
"height": 892
|
||||
|
|
@ -1345,7 +1345,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4a (5G) landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"height": 892,
|
||||
"width": 412
|
||||
|
|
@ -1360,7 +1360,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 393,
|
||||
"height": 851
|
||||
|
|
@ -1375,7 +1375,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 851,
|
||||
"height": 393
|
||||
|
|
@ -1390,7 +1390,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 7": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 412,
|
||||
"height": 915
|
||||
|
|
@ -1405,7 +1405,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 7 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 915,
|
||||
"height": 412
|
||||
|
|
@ -1420,7 +1420,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Moto G4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1431,7 +1431,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Moto G4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1442,7 +1442,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Chrome HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Safari/537.36",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1457,7 +1457,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Edge HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Safari/537.36 Edg/125.0.6422.41",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Safari/537.36 Edg/126.0.6478.8",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1472,7 +1472,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Firefox HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0.1) Gecko/20100101 Firefox/125.0.1",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1502,7 +1502,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Desktop Chrome": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Safari/537.36",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
@ -1517,7 +1517,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Edge": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.41 Safari/537.36 Edg/125.0.6422.41",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.8 Safari/537.36 Edg/126.0.6478.8",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
@ -1532,7 +1532,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Firefox": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0.1) Gecko/20100101 Firefox/125.0.1",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ export class Electron extends SdkObject {
|
|||
return controller.run(async progress => {
|
||||
let app: ElectronApplication | undefined = undefined;
|
||||
// --remote-debugging-port=0 must be the last playwright's argument, loader.ts relies on it.
|
||||
const electronArguments = ['--inspect=0', '--remote-debugging-port=0', ...args];
|
||||
let electronArguments = ['--inspect=0', '--remote-debugging-port=0', ...args];
|
||||
|
||||
if (os.platform() === 'linux') {
|
||||
const runningAsRoot = process.geteuid && process.geteuid() === 0;
|
||||
|
|
@ -195,6 +195,16 @@ export class Electron extends SdkObject {
|
|||
// Packaged apps might have their own command line handling.
|
||||
electronArguments.unshift('-r', require.resolve('./loader'));
|
||||
}
|
||||
let shell = false;
|
||||
if (process.platform === 'win32') {
|
||||
// On Windows in order to run .cmd files, shell: true is required.
|
||||
// https://github.com/nodejs/node/issues/52554
|
||||
shell = true;
|
||||
// On Windows, we need to quote the executable path due to shell: true.
|
||||
command = `"${command}"`;
|
||||
// On Windows, we need to quote the arguments due to shell: true.
|
||||
electronArguments = electronArguments.map(arg => `"${arg}"`);
|
||||
}
|
||||
|
||||
// When debugging Playwright test that runs Electron, NODE_OPTIONS
|
||||
// will make the debugger attach to Electron's Node. But Playwright
|
||||
|
|
@ -208,9 +218,7 @@ export class Electron extends SdkObject {
|
|||
progress.log(message);
|
||||
browserLogsCollector.log(message);
|
||||
},
|
||||
// On Windows in order to run .cmd files, shell: true is required.
|
||||
// https://github.com/nodejs/node/issues/52554
|
||||
shell: process.platform === 'win32',
|
||||
shell,
|
||||
stdio: 'pipe',
|
||||
cwd: options.cwd,
|
||||
tempDirectories: [artifactsDir],
|
||||
|
|
@ -221,6 +229,8 @@ export class Electron extends SdkObject {
|
|||
onExit: () => app?.emit(ElectronApplication.Events.Close),
|
||||
});
|
||||
|
||||
// All waitForLines must be started immediately.
|
||||
// Otherwise the lines might come before we are ready.
|
||||
const waitForXserverError = new Promise(async (resolve, reject) => {
|
||||
waitForLine(progress, launchedProcess, /Unable to open X display/).then(() => reject(new Error([
|
||||
'Unable to open X display!',
|
||||
|
|
@ -232,17 +242,20 @@ export class Electron extends SdkObject {
|
|||
progress.metadata.log
|
||||
].join('\n')))).catch(() => {});
|
||||
});
|
||||
const nodeMatchPromise = waitForLine(progress, launchedProcess, /^Debugger listening on (ws:\/\/.*)$/);
|
||||
const chromeMatchPromise = waitForLine(progress, launchedProcess, /^DevTools listening on (ws:\/\/.*)$/);
|
||||
const debuggerDisconnectPromise = waitForLine(progress, launchedProcess, /Waiting for the debugger to disconnect\.\.\./);
|
||||
|
||||
const nodeMatch = await waitForLine(progress, launchedProcess, /^Debugger listening on (ws:\/\/.*)$/);
|
||||
const nodeMatch = await nodeMatchPromise;
|
||||
const nodeTransport = await WebSocketTransport.connect(progress, nodeMatch[1]);
|
||||
const nodeConnection = new CRConnection(nodeTransport, helper.debugProtocolLogger(), browserLogsCollector);
|
||||
|
||||
// Immediately release exiting process under debug.
|
||||
waitForLine(progress, launchedProcess, /Waiting for the debugger to disconnect\.\.\./).then(() => {
|
||||
debuggerDisconnectPromise.then(() => {
|
||||
nodeTransport.close();
|
||||
}).catch(() => {});
|
||||
const chromeMatch = await Promise.race([
|
||||
waitForLine(progress, launchedProcess, /^DevTools listening on (ws:\/\/.*)$/),
|
||||
chromeMatchPromise,
|
||||
waitForXserverError,
|
||||
]) as RegExpMatchArray;
|
||||
const chromeTransport = await WebSocketTransport.connect(progress, chromeMatch[1]);
|
||||
|
|
|
|||
|
|
@ -336,12 +336,15 @@ export abstract class APIRequestContext extends SdkObject {
|
|||
redirectOptions.rejectUnauthorized = false;
|
||||
|
||||
// HTTP-redirect fetch step 4: If locationURL is null, then return response.
|
||||
if (response.headers.location) {
|
||||
// Best-effort UTF-8 decoding, per spec it's US-ASCII only, but browsers are more lenient.
|
||||
// Node.js parses it as Latin1 via std::v8::String, so we convert it to UTF-8.
|
||||
const locationHeaderValue = Buffer.from(response.headers.location ?? '', 'latin1').toString('utf8');
|
||||
if (locationHeaderValue) {
|
||||
let locationURL;
|
||||
try {
|
||||
locationURL = new URL(response.headers.location, url);
|
||||
locationURL = new URL(locationHeaderValue, url);
|
||||
} catch (error) {
|
||||
reject(new Error(`uri requested responds with an invalid redirect URL: ${response.headers.location}`));
|
||||
reject(new Error(`uri requested responds with an invalid redirect URL: ${locationHeaderValue}`));
|
||||
request.destroy();
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ class FFRouteImpl implements network.RouteDelegate {
|
|||
await this._session.sendMayFail('Network.fulfillInterceptedRequest', {
|
||||
requestId: this._request._id,
|
||||
status: response.status,
|
||||
statusText: network.STATUS_TEXTS[String(response.status)] || '',
|
||||
statusText: network.statusText(response.status),
|
||||
headers: response.headers,
|
||||
base64body,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -616,7 +616,7 @@ export interface RouteDelegate {
|
|||
}
|
||||
|
||||
// List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes.
|
||||
export const STATUS_TEXTS: { [status: string]: string } = {
|
||||
const STATUS_TEXTS: { [status: string]: string } = {
|
||||
'100': 'Continue',
|
||||
'101': 'Switching Protocols',
|
||||
'102': 'Processing',
|
||||
|
|
@ -682,6 +682,10 @@ export const STATUS_TEXTS: { [status: string]: string } = {
|
|||
'511': 'Network Authentication Required',
|
||||
};
|
||||
|
||||
export function statusText(status: number): string {
|
||||
return STATUS_TEXTS[String(status)] || 'Unknown';
|
||||
}
|
||||
|
||||
export function singleHeader(name: string, value: string): HeadersArray {
|
||||
return [{ name, value }];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,17 +137,17 @@ const DOWNLOAD_PATHS: Record<BrowserName | InternalTool, DownloadPaths> = {
|
|||
'debian11-arm64': 'builds/firefox/%s/firefox-debian-11-arm64.zip',
|
||||
'debian12-x64': 'builds/firefox/%s/firefox-debian-12.zip',
|
||||
'debian12-arm64': 'builds/firefox/%s/firefox-debian-12-arm64.zip',
|
||||
'mac10.13': 'builds/firefox/%s/firefox-mac-13.zip',
|
||||
'mac10.14': 'builds/firefox/%s/firefox-mac-13.zip',
|
||||
'mac10.15': 'builds/firefox/%s/firefox-mac-13.zip',
|
||||
'mac11': 'builds/firefox/%s/firefox-mac-13.zip',
|
||||
'mac11-arm64': 'builds/firefox/%s/firefox-mac-13-arm64.zip',
|
||||
'mac12': 'builds/firefox/%s/firefox-mac-13.zip',
|
||||
'mac12-arm64': 'builds/firefox/%s/firefox-mac-13-arm64.zip',
|
||||
'mac13': 'builds/firefox/%s/firefox-mac-13.zip',
|
||||
'mac13-arm64': 'builds/firefox/%s/firefox-mac-13-arm64.zip',
|
||||
'mac14': 'builds/firefox/%s/firefox-mac-13.zip',
|
||||
'mac14-arm64': 'builds/firefox/%s/firefox-mac-13-arm64.zip',
|
||||
'mac10.13': 'builds/firefox/%s/firefox-mac.zip',
|
||||
'mac10.14': 'builds/firefox/%s/firefox-mac.zip',
|
||||
'mac10.15': 'builds/firefox/%s/firefox-mac.zip',
|
||||
'mac11': 'builds/firefox/%s/firefox-mac.zip',
|
||||
'mac11-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
|
||||
'mac12': 'builds/firefox/%s/firefox-mac.zip',
|
||||
'mac12-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
|
||||
'mac13': 'builds/firefox/%s/firefox-mac.zip',
|
||||
'mac13-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
|
||||
'mac14': 'builds/firefox/%s/firefox-mac.zip',
|
||||
'mac14-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
|
||||
'win64': 'builds/firefox/%s/firefox-win64.zip',
|
||||
},
|
||||
'firefox-beta': {
|
||||
|
|
@ -162,17 +162,17 @@ const DOWNLOAD_PATHS: Record<BrowserName | InternalTool, DownloadPaths> = {
|
|||
'debian11-arm64': 'builds/firefox-beta/%s/firefox-beta-debian-11-arm64.zip',
|
||||
'debian12-x64': 'builds/firefox-beta/%s/firefox-beta-debian-12.zip',
|
||||
'debian12-arm64': 'builds/firefox-beta/%s/firefox-beta-debian-12-arm64.zip',
|
||||
'mac10.13': 'builds/firefox-beta/%s/firefox-beta-mac-13.zip',
|
||||
'mac10.14': 'builds/firefox-beta/%s/firefox-beta-mac-13.zip',
|
||||
'mac10.15': 'builds/firefox-beta/%s/firefox-beta-mac-13.zip',
|
||||
'mac11': 'builds/firefox-beta/%s/firefox-beta-mac-13.zip',
|
||||
'mac11-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-13-arm64.zip',
|
||||
'mac12': 'builds/firefox-beta/%s/firefox-beta-mac-13.zip',
|
||||
'mac12-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-13-arm64.zip',
|
||||
'mac13': 'builds/firefox-beta/%s/firefox-beta-mac-13.zip',
|
||||
'mac13-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-13-arm64.zip',
|
||||
'mac14': 'builds/firefox-beta/%s/firefox-beta-mac-13.zip',
|
||||
'mac14-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-13-arm64.zip',
|
||||
'mac10.13': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
|
||||
'mac10.14': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
|
||||
'mac10.15': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
|
||||
'mac11': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
|
||||
'mac11-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
|
||||
'mac12': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
|
||||
'mac12-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
|
||||
'mac13': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
|
||||
'mac13-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
|
||||
'mac14': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
|
||||
'mac14-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
|
||||
'win64': 'builds/firefox-beta/%s/firefox-beta-win64.zip',
|
||||
},
|
||||
'webkit': {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ export type TraceViewerRedirectOptions = {
|
|||
reporter?: string[];
|
||||
webApp?: string;
|
||||
isServer?: boolean;
|
||||
outputDir?: string;
|
||||
};
|
||||
|
||||
export type TraceViewerAppOptions = {
|
||||
|
|
@ -129,10 +130,12 @@ export async function installRootRedirect(server: HttpServer, traceUrls: string[
|
|||
params.append('timeout', String(options.timeout));
|
||||
if (options.headed)
|
||||
params.append('headed', '');
|
||||
if (options.outputDir)
|
||||
params.append('outputDir', options.outputDir);
|
||||
for (const reporter of options.reporter || [])
|
||||
params.append('reporter', reporter);
|
||||
|
||||
const urlPath = `/trace/${options.webApp || 'index.html'}?${params.toString()}`;
|
||||
const urlPath = `./trace/${options.webApp || 'index.html'}?${params.toString()}`;
|
||||
server.routePath('/', (_, response) => {
|
||||
response.statusCode = 302;
|
||||
response.setHeader('Location', urlPath);
|
||||
|
|
@ -145,7 +148,7 @@ export async function runTraceViewerApp(traceUrls: string[], browserName: string
|
|||
validateTraceUrls(traceUrls);
|
||||
const server = await startTraceViewerServer(options);
|
||||
await installRootRedirect(server, traceUrls, options);
|
||||
const page = await openTraceViewerApp(server.urlPrefix(), browserName, options);
|
||||
const page = await openTraceViewerApp(server.urlPrefix('precise'), browserName, options);
|
||||
if (exitOnClose)
|
||||
page.on('close', () => gracefullyProcessExitDoNotHang(0));
|
||||
return page;
|
||||
|
|
@ -155,7 +158,7 @@ export async function runTraceInBrowser(traceUrls: string[], options: TraceViewe
|
|||
validateTraceUrls(traceUrls);
|
||||
const server = await startTraceViewerServer(options);
|
||||
await installRootRedirect(server, traceUrls, options);
|
||||
await openTraceInBrowser(server.urlPrefix());
|
||||
await openTraceInBrowser(server.urlPrefix('human-readable'));
|
||||
}
|
||||
|
||||
export async function openTraceViewerApp(url: string, browserName: string, options?: TraceViewerAppOptions): Promise<Page> {
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export class WKRouteImpl implements network.RouteDelegate {
|
|||
await this._session.sendMayFail('Network.interceptRequestWithResponse', {
|
||||
requestId: this._requestId,
|
||||
status: response.status,
|
||||
statusText: network.STATUS_TEXTS[String(response.status)],
|
||||
statusText: network.statusText(response.status),
|
||||
mimeType,
|
||||
headers,
|
||||
base64Encoded: response.isBase64,
|
||||
|
|
|
|||
|
|
@ -21,9 +21,13 @@ export function getFromENV(name: string): string | undefined {
|
|||
return value;
|
||||
}
|
||||
|
||||
export function getAsBooleanFromENV(name: string): boolean {
|
||||
export function getAsBooleanFromENV(name: string, defaultValue?: boolean | undefined): boolean {
|
||||
const value = getFromENV(name);
|
||||
return !!value && value !== 'false' && value !== '0';
|
||||
if (value === 'false' || value === '0')
|
||||
return false;
|
||||
if (value)
|
||||
return true;
|
||||
return !!defaultValue;
|
||||
}
|
||||
|
||||
export function getPackageManager() {
|
||||
|
|
|
|||
|
|
@ -34,14 +34,14 @@ export type Transport = {
|
|||
|
||||
export class HttpServer {
|
||||
private _server: http.Server;
|
||||
private _urlPrefix: string;
|
||||
private _urlPrefixPrecise: string = '';
|
||||
private _urlPrefixHumanReadable: string = '';
|
||||
private _port: number = 0;
|
||||
private _started = false;
|
||||
private _routes: { prefix?: string, exact?: string, handler: ServerRouteHandler }[] = [];
|
||||
private _wsGuid: string | undefined;
|
||||
|
||||
constructor(address: string = '') {
|
||||
this._urlPrefix = address;
|
||||
constructor() {
|
||||
this._server = createHttpServer(this._onRequest.bind(this));
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ export class HttpServer {
|
|||
return this._wsGuid;
|
||||
}
|
||||
|
||||
async start(options: { port?: number, preferredPort?: number, host?: string } = {}): Promise<string> {
|
||||
async start(options: { port?: number, preferredPort?: number, host?: string } = {}): Promise<void> {
|
||||
assert(!this._started, 'server already started');
|
||||
this._started = true;
|
||||
|
||||
|
|
@ -121,24 +121,23 @@ export class HttpServer {
|
|||
|
||||
const address = this._server.address();
|
||||
assert(address, 'Could not bind server socket');
|
||||
if (!this._urlPrefix) {
|
||||
if (typeof address === 'string') {
|
||||
this._urlPrefix = address;
|
||||
} else {
|
||||
this._port = address.port;
|
||||
const resolvedHost = address.family === 'IPv4' ? address.address : `[${address.address}]`;
|
||||
this._urlPrefix = `http://${resolvedHost}:${address.port}`;
|
||||
}
|
||||
if (typeof address === 'string') {
|
||||
this._urlPrefixPrecise = address;
|
||||
this._urlPrefixHumanReadable = address;
|
||||
} else {
|
||||
this._port = address.port;
|
||||
const resolvedHost = address.family === 'IPv4' ? address.address : `[${address.address}]`;
|
||||
this._urlPrefixPrecise = `http://${resolvedHost}:${address.port}`;
|
||||
this._urlPrefixHumanReadable = `http://${host}:${address.port}`;
|
||||
}
|
||||
return this._urlPrefix;
|
||||
}
|
||||
|
||||
async stop() {
|
||||
await new Promise(cb => this._server!.close(cb));
|
||||
}
|
||||
|
||||
urlPrefix(): string {
|
||||
return this._urlPrefix;
|
||||
urlPrefix(purpose: 'human-readable' | 'precise'): string {
|
||||
return purpose === 'human-readable' ? this._urlPrefixHumanReadable : this._urlPrefixPrecise;
|
||||
}
|
||||
|
||||
serveFile(request: http.IncomingMessage, response: http.ServerResponse, absoluteFilePath: string, headers?: { [name: string]: string }): boolean {
|
||||
|
|
|
|||
137
packages/playwright-core/types/protocol.d.ts
vendored
137
packages/playwright-core/types/protocol.d.ts
vendored
|
|
@ -841,6 +841,7 @@ CORS RFC1918 enforcement.
|
|||
clientSecurityState?: Network.ClientSecurityState;
|
||||
}
|
||||
export type AttributionReportingIssueType = "PermissionPolicyDisabled"|"UntrustworthyReportingOrigin"|"InsecureContext"|"InvalidHeader"|"InvalidRegisterTriggerHeader"|"SourceAndTriggerHeaders"|"SourceIgnored"|"TriggerIgnored"|"OsSourceIgnored"|"OsTriggerIgnored"|"InvalidRegisterOsSourceHeader"|"InvalidRegisterOsTriggerHeader"|"WebAndOsHeaders"|"NoWebOrOsSupport"|"NavigationRegistrationWithoutTransientUserActivation"|"InvalidInfoHeader"|"NoRegisterSourceHeader"|"NoRegisterTriggerHeader"|"NoRegisterOsSourceHeader"|"NoRegisterOsTriggerHeader";
|
||||
export type SharedDictionaryError = "UseErrorCrossOriginNoCorsRequest"|"UseErrorDictionaryLoadFailure"|"UseErrorMatchingDictionaryNotUsed"|"UseErrorUnexpectedContentDictionaryHeader"|"WriteErrorCossOriginNoCorsRequest"|"WriteErrorDisallowedBySettings"|"WriteErrorExpiredResponse"|"WriteErrorFeatureDisabled"|"WriteErrorInsufficientResources"|"WriteErrorInvalidMatchField"|"WriteErrorInvalidStructuredHeader"|"WriteErrorNavigationRequest"|"WriteErrorNoMatchField"|"WriteErrorNonListMatchDestField"|"WriteErrorNonSecureContext"|"WriteErrorNonStringIdField"|"WriteErrorNonStringInMatchDestList"|"WriteErrorNonStringMatchField"|"WriteErrorNonTokenTypeField"|"WriteErrorRequestAborted"|"WriteErrorShuttingDown"|"WriteErrorTooLongIdField"|"WriteErrorUnsupportedType";
|
||||
/**
|
||||
* Details for issues around "Attribution Reporting API" usage.
|
||||
Explainer: https://github.com/WICG/attribution-reporting-api
|
||||
|
|
@ -870,6 +871,10 @@ instead of "limited-quirks".
|
|||
url: string;
|
||||
location?: SourceCodeLocation;
|
||||
}
|
||||
export interface SharedDictionaryIssueDetails {
|
||||
sharedDictionaryError: SharedDictionaryError;
|
||||
request: AffectedRequest;
|
||||
}
|
||||
export type GenericIssueErrorType = "CrossOriginPortalPostMessageError"|"FormLabelForNameError"|"FormDuplicateIdForInputError"|"FormInputWithNoLabelError"|"FormAutocompleteAttributeEmptyError"|"FormEmptyIdAndNameAttributesForInputError"|"FormAriaLabelledByToNonExistingId"|"FormInputAssignedAutocompleteValueToIdOrNameAttributeError"|"FormLabelHasNeitherForNorNestedInput"|"FormLabelForMatchesNonExistingIdError"|"FormInputHasWrongButWellIntendedAutocompleteValueError"|"ResponseWasBlockedByORB";
|
||||
/**
|
||||
* Depending on the concrete errorType, different properties are set.
|
||||
|
|
@ -997,7 +1002,7 @@ registrations being ignored.
|
|||
optional fields in InspectorIssueDetails to convey more specific
|
||||
information about the kind of issue.
|
||||
*/
|
||||
export type InspectorIssueCode = "CookieIssue"|"MixedContentIssue"|"BlockedByResponseIssue"|"HeavyAdIssue"|"ContentSecurityPolicyIssue"|"SharedArrayBufferIssue"|"LowTextContrastIssue"|"CorsIssue"|"AttributionReportingIssue"|"QuirksModeIssue"|"NavigatorUserAgentIssue"|"GenericIssue"|"DeprecationIssue"|"ClientHintIssue"|"FederatedAuthRequestIssue"|"BounceTrackingIssue"|"CookieDeprecationMetadataIssue"|"StylesheetLoadingIssue"|"FederatedAuthUserInfoRequestIssue"|"PropertyRuleIssue";
|
||||
export type InspectorIssueCode = "CookieIssue"|"MixedContentIssue"|"BlockedByResponseIssue"|"HeavyAdIssue"|"ContentSecurityPolicyIssue"|"SharedArrayBufferIssue"|"LowTextContrastIssue"|"CorsIssue"|"AttributionReportingIssue"|"QuirksModeIssue"|"NavigatorUserAgentIssue"|"GenericIssue"|"DeprecationIssue"|"ClientHintIssue"|"FederatedAuthRequestIssue"|"BounceTrackingIssue"|"CookieDeprecationMetadataIssue"|"StylesheetLoadingIssue"|"FederatedAuthUserInfoRequestIssue"|"PropertyRuleIssue"|"SharedDictionaryIssue";
|
||||
/**
|
||||
* This struct holds a list of optional fields with additional information
|
||||
specific to the kind of issue. When adding a new issue code, please also
|
||||
|
|
@ -1024,6 +1029,7 @@ add a new optional field to this type.
|
|||
stylesheetLoadingIssueDetails?: StylesheetLoadingIssueDetails;
|
||||
propertyRuleIssueDetails?: PropertyRuleIssueDetails;
|
||||
federatedAuthUserInfoRequestIssueDetails?: FederatedAuthUserInfoRequestIssueDetails;
|
||||
sharedDictionaryIssueDetails?: SharedDictionaryIssueDetails;
|
||||
}
|
||||
/**
|
||||
* A unique id for a DevTools inspector issue. Allows other entities (e.g.
|
||||
|
|
@ -1121,6 +1127,33 @@ using Audits.issueAdded event.
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines commands and events for browser extensions. Available if the client
|
||||
is connected using the --remote-debugging-pipe flag and
|
||||
the --enable-unsafe-extension-debugging flag is set.
|
||||
*/
|
||||
export module Extensions {
|
||||
|
||||
|
||||
/**
|
||||
* Installs an unpacked extension from the filesystem similar to
|
||||
--load-extension CLI flags. Returns extension ID once the extension
|
||||
has been installed.
|
||||
*/
|
||||
export type loadUnpackedParameters = {
|
||||
/**
|
||||
* Absolute file path.
|
||||
*/
|
||||
path: string;
|
||||
}
|
||||
export type loadUnpackedReturnValue = {
|
||||
/**
|
||||
* Extension id.
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines commands and events for Autofill.
|
||||
*/
|
||||
|
|
@ -3450,7 +3483,7 @@ front-end.
|
|||
/**
|
||||
* Pseudo element type.
|
||||
*/
|
||||
export type PseudoType = "first-line"|"first-letter"|"before"|"after"|"marker"|"backdrop"|"selection"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-markers"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new";
|
||||
export type PseudoType = "first-line"|"first-letter"|"before"|"after"|"marker"|"backdrop"|"selection"|"search-text"|"target-text"|"spelling-error"|"grammar-error"|"highlight"|"first-line-inherited"|"scroll-marker"|"scroll-markers"|"scrollbar"|"scrollbar-thumb"|"scrollbar-button"|"scrollbar-track"|"scrollbar-track-piece"|"scrollbar-corner"|"resizer"|"input-list-button"|"view-transition"|"view-transition-group"|"view-transition-image-pair"|"view-transition-old"|"view-transition-new";
|
||||
/**
|
||||
* Shadow root type.
|
||||
*/
|
||||
|
|
@ -4426,6 +4459,25 @@ appear on top of all other content.
|
|||
*/
|
||||
nodeIds: NodeId[];
|
||||
}
|
||||
/**
|
||||
* Returns the NodeId of the matched element according to certain relations.
|
||||
*/
|
||||
export type getElementByRelationParameters = {
|
||||
/**
|
||||
* Id of the node from which to query the relation.
|
||||
*/
|
||||
nodeId: NodeId;
|
||||
/**
|
||||
* Type of relation to get.
|
||||
*/
|
||||
relation: "PopoverTarget";
|
||||
}
|
||||
export type getElementByRelationReturnValue = {
|
||||
/**
|
||||
* NodeId of the element matching the queried relation.
|
||||
*/
|
||||
nodeId: NodeId;
|
||||
}
|
||||
/**
|
||||
* Re-does the last undone action.
|
||||
*/
|
||||
|
|
@ -8309,8 +8361,16 @@ records.
|
|||
*/
|
||||
export type ServiceWorkerRouterSource = "network"|"cache"|"fetch-event"|"race-network-and-fetch-handler";
|
||||
export interface ServiceWorkerRouterInfo {
|
||||
ruleIdMatched: number;
|
||||
matchedSourceType: ServiceWorkerRouterSource;
|
||||
/**
|
||||
* ID of the rule matched. If there is a matched rule, this field will
|
||||
be set, otherwiser no value will be set.
|
||||
*/
|
||||
ruleIdMatched?: number;
|
||||
/**
|
||||
* The router source of the matched rule. If there is a matched rule, this
|
||||
field will be set, otherwise no value will be set.
|
||||
*/
|
||||
matchedSourceType?: ServiceWorkerRouterSource;
|
||||
}
|
||||
/**
|
||||
* HTTP response data.
|
||||
|
|
@ -8385,7 +8445,10 @@ records.
|
|||
*/
|
||||
fromEarlyHints?: boolean;
|
||||
/**
|
||||
* Information about how Service Worker Static Router was used.
|
||||
* Information about how ServiceWorker Static Router API was used. If this
|
||||
field is set with `matchedSourceType` field, a matching rule is found.
|
||||
If this field is set without `matchedSource`, no matching rule is found.
|
||||
Otherwise, the API is not used.
|
||||
*/
|
||||
serviceWorkerRouterInfo?: ServiceWorkerRouterInfo;
|
||||
/**
|
||||
|
|
@ -11167,7 +11230,7 @@ as an ad.
|
|||
* All Permissions Policy features. This enum should match the one defined
|
||||
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"|"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"|"window-placement"|"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"|"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.
|
||||
*/
|
||||
|
|
@ -11755,7 +11818,7 @@ https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-expl
|
|||
/**
|
||||
* 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"|"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";
|
||||
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"|"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";
|
||||
/**
|
||||
* Types of not restored reasons for back-forward cache.
|
||||
*/
|
||||
|
|
@ -12334,7 +12397,7 @@ option, use with caution.
|
|||
This API always waits for the manifest to be loaded.
|
||||
If manifestId is provided, and it does not match the manifest of the
|
||||
current document, this API errors out.
|
||||
If there isn’t a loaded page, this API errors out immediately.
|
||||
If there is not a loaded page, this API errors out immediately.
|
||||
*/
|
||||
export type getAppManifestParameters = {
|
||||
manifestId?: string;
|
||||
|
|
@ -16690,7 +16753,7 @@ possible for multiple rule sets and links to trigger a single attempt.
|
|||
/**
|
||||
* 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";
|
||||
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";
|
||||
/**
|
||||
* Preloading status values, see also PreloadingTriggeringOutcome. This
|
||||
status is shared by prefetchStatusUpdated and prerenderStatusUpdated.
|
||||
|
|
@ -16921,6 +16984,52 @@ https://web.dev/learn/pwa/web-app-manifest.
|
|||
badgeCount: number;
|
||||
fileHandlers: FileHandler[];
|
||||
}
|
||||
/**
|
||||
* Installs the given manifest identity, optionally using the given install_url
|
||||
or IWA bundle location.
|
||||
|
||||
TODO(crbug.com/337872319) Support IWA to meet the following specific
|
||||
requirement.
|
||||
IWA-specific install description: If the manifest_id is isolated-app://,
|
||||
install_url_or_bundle_url is required, and can be either an http(s) URL or
|
||||
file:// URL pointing to a signed web bundle (.swbn). The .swbn file's
|
||||
signing key must correspond to manifest_id. If Chrome is not in IWA dev
|
||||
mode, the installation will fail, regardless of the state of the allowlist.
|
||||
*/
|
||||
export type installParameters = {
|
||||
manifestId: string;
|
||||
/**
|
||||
* The location of the app or bundle overriding the one derived from the
|
||||
manifestId.
|
||||
*/
|
||||
installUrlOrBundleUrl?: string;
|
||||
}
|
||||
export type installReturnValue = {
|
||||
}
|
||||
/**
|
||||
* Uninstals the given manifest_id and closes any opened app windows.
|
||||
*/
|
||||
export type uninstallParameters = {
|
||||
manifestId: string;
|
||||
}
|
||||
export type uninstallReturnValue = {
|
||||
}
|
||||
/**
|
||||
* Launches the installed web app, or an url in the same web app instead of the
|
||||
default start url if it is provided. Returns a tab / web contents based
|
||||
Target.TargetID which can be used to attach to via Target.attachToTarget or
|
||||
similar APIs.
|
||||
*/
|
||||
export type launchParameters = {
|
||||
manifestId: string;
|
||||
url?: string;
|
||||
}
|
||||
export type launchReturnValue = {
|
||||
/**
|
||||
* ID of the tab target created as a result.
|
||||
*/
|
||||
targetId: Target.TargetID;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -19773,6 +19882,7 @@ Error was thrown.
|
|||
"Audits.enable": Audits.enableParameters;
|
||||
"Audits.checkContrast": Audits.checkContrastParameters;
|
||||
"Audits.checkFormsIssues": Audits.checkFormsIssuesParameters;
|
||||
"Extensions.loadUnpacked": Extensions.loadUnpackedParameters;
|
||||
"Autofill.trigger": Autofill.triggerParameters;
|
||||
"Autofill.setAddresses": Autofill.setAddressesParameters;
|
||||
"Autofill.disable": Autofill.disableParameters;
|
||||
|
|
@ -19870,6 +19980,7 @@ Error was thrown.
|
|||
"DOM.querySelector": DOM.querySelectorParameters;
|
||||
"DOM.querySelectorAll": DOM.querySelectorAllParameters;
|
||||
"DOM.getTopLayerElements": DOM.getTopLayerElementsParameters;
|
||||
"DOM.getElementByRelation": DOM.getElementByRelationParameters;
|
||||
"DOM.redo": DOM.redoParameters;
|
||||
"DOM.removeAttribute": DOM.removeAttributeParameters;
|
||||
"DOM.removeNode": DOM.removeNodeParameters;
|
||||
|
|
@ -20256,6 +20367,9 @@ Error was thrown.
|
|||
"FedCm.dismissDialog": FedCm.dismissDialogParameters;
|
||||
"FedCm.resetCooldown": FedCm.resetCooldownParameters;
|
||||
"PWA.getOsAppState": PWA.getOsAppStateParameters;
|
||||
"PWA.install": PWA.installParameters;
|
||||
"PWA.uninstall": PWA.uninstallParameters;
|
||||
"PWA.launch": PWA.launchParameters;
|
||||
"Console.clearMessages": Console.clearMessagesParameters;
|
||||
"Console.disable": Console.disableParameters;
|
||||
"Console.enable": Console.enableParameters;
|
||||
|
|
@ -20361,6 +20475,7 @@ Error was thrown.
|
|||
"Audits.enable": Audits.enableReturnValue;
|
||||
"Audits.checkContrast": Audits.checkContrastReturnValue;
|
||||
"Audits.checkFormsIssues": Audits.checkFormsIssuesReturnValue;
|
||||
"Extensions.loadUnpacked": Extensions.loadUnpackedReturnValue;
|
||||
"Autofill.trigger": Autofill.triggerReturnValue;
|
||||
"Autofill.setAddresses": Autofill.setAddressesReturnValue;
|
||||
"Autofill.disable": Autofill.disableReturnValue;
|
||||
|
|
@ -20458,6 +20573,7 @@ Error was thrown.
|
|||
"DOM.querySelector": DOM.querySelectorReturnValue;
|
||||
"DOM.querySelectorAll": DOM.querySelectorAllReturnValue;
|
||||
"DOM.getTopLayerElements": DOM.getTopLayerElementsReturnValue;
|
||||
"DOM.getElementByRelation": DOM.getElementByRelationReturnValue;
|
||||
"DOM.redo": DOM.redoReturnValue;
|
||||
"DOM.removeAttribute": DOM.removeAttributeReturnValue;
|
||||
"DOM.removeNode": DOM.removeNodeReturnValue;
|
||||
|
|
@ -20844,6 +20960,9 @@ Error was thrown.
|
|||
"FedCm.dismissDialog": FedCm.dismissDialogReturnValue;
|
||||
"FedCm.resetCooldown": FedCm.resetCooldownReturnValue;
|
||||
"PWA.getOsAppState": PWA.getOsAppStateReturnValue;
|
||||
"PWA.install": PWA.installReturnValue;
|
||||
"PWA.uninstall": PWA.uninstallReturnValue;
|
||||
"PWA.launch": PWA.launchReturnValue;
|
||||
"Console.clearMessages": Console.clearMessagesReturnValue;
|
||||
"Console.disable": Console.disableReturnValue;
|
||||
"Console.enable": Console.enableReturnValue;
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export type FixturesWithLocation = {
|
|||
fixtures: Fixtures;
|
||||
location: Location;
|
||||
};
|
||||
export type Annotation = { type: string, description?: string };
|
||||
export type Annotation = { type: string, description?: string, url?: string };
|
||||
|
||||
export const defaultTimeout = 30000;
|
||||
|
||||
|
|
@ -52,6 +52,7 @@ export class FullConfigInternal {
|
|||
cliProjectFilter?: string[];
|
||||
cliListOnly = false;
|
||||
cliPassWithNoTests?: boolean;
|
||||
cliFailOnFlakyTests?: boolean;
|
||||
testIdMatcher?: Matcher;
|
||||
defineConfigWasUsed = false;
|
||||
|
||||
|
|
@ -266,7 +267,7 @@ export function toReporters(reporters: BuiltInReporter | ReporterDescription[] |
|
|||
export const builtInReporters = ['list', 'line', 'dot', 'json', 'junit', 'null', 'github', 'html', 'blob', 'markdown'] as const;
|
||||
export type BuiltInReporter = typeof builtInReporters[number];
|
||||
|
||||
export type ContextReuseMode = 'none' | 'force' | 'when-possible';
|
||||
export type ContextReuseMode = 'none' | 'when-possible';
|
||||
|
||||
function resolveScript(id: string | undefined, rootDir: string): string | undefined {
|
||||
if (!id)
|
||||
|
|
|
|||
|
|
@ -44,11 +44,12 @@ export class ProcessRunner {
|
|||
}
|
||||
}
|
||||
|
||||
let closed = false;
|
||||
let gracefullyCloseCalled = false;
|
||||
let forceExitInitiated = false;
|
||||
|
||||
sendMessageToParent({ method: 'ready' });
|
||||
|
||||
process.on('disconnect', gracefullyCloseAndExit);
|
||||
process.on('disconnect', () => gracefullyCloseAndExit(true));
|
||||
process.on('SIGINT', () => {});
|
||||
process.on('SIGTERM', () => {});
|
||||
|
||||
|
|
@ -76,7 +77,7 @@ process.on('message', async (message: any) => {
|
|||
const keys = new Set([...Object.keys(process.env), ...Object.keys(startingEnv)]);
|
||||
const producedEnv: EnvProducedPayload = [...keys].filter(key => startingEnv[key] !== process.env[key]).map(key => [key, process.env[key] ?? null]);
|
||||
sendMessageToParent({ method: '__env_produced__', params: producedEnv });
|
||||
await gracefullyCloseAndExit();
|
||||
await gracefullyCloseAndExit(false);
|
||||
return;
|
||||
}
|
||||
if (message.method === '__dispatch__') {
|
||||
|
|
@ -92,19 +93,24 @@ process.on('message', async (message: any) => {
|
|||
}
|
||||
});
|
||||
|
||||
async function gracefullyCloseAndExit() {
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
// Force exit after 30 seconds.
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
setTimeout(() => process.exit(0), 30000);
|
||||
// Meanwhile, try to gracefully shutdown.
|
||||
await processRunner?.gracefullyClose().catch(() => {});
|
||||
if (processName)
|
||||
await stopProfiling(processName).catch(() => {});
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
process.exit(0);
|
||||
const kForceExitTimeout = +(process.env.PWTEST_FORCE_EXIT_TIMEOUT || 30000);
|
||||
|
||||
async function gracefullyCloseAndExit(forceExit: boolean) {
|
||||
if (forceExit && !forceExitInitiated) {
|
||||
forceExitInitiated = true;
|
||||
// Force exit after 30 seconds.
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
setTimeout(() => process.exit(0), kForceExitTimeout);
|
||||
}
|
||||
if (!gracefullyCloseCalled) {
|
||||
gracefullyCloseCalled = true;
|
||||
// Meanwhile, try to gracefully shutdown.
|
||||
await processRunner?.gracefullyClose().catch(() => {});
|
||||
if (processName)
|
||||
await stopProfiling(processName).catch(() => {});
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessageToParent(message: { method: string, params?: any }) {
|
||||
|
|
|
|||
|
|
@ -356,8 +356,8 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
|||
_reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
|
||||
let mode = _optionContextReuseMode;
|
||||
if (process.env.PW_TEST_REUSE_CONTEXT)
|
||||
mode = process.env.PW_TEST_REUSE_CONTEXT === 'when-possible' ? 'when-possible' : (process.env.PW_TEST_REUSE_CONTEXT ? 'force' : 'none');
|
||||
const reuse = mode === 'force' || (mode === 'when-possible' && normalizeVideoMode(video) === 'off');
|
||||
mode = 'when-possible';
|
||||
const reuse = mode === 'when-possible' && normalizeVideoMode(video) === 'off';
|
||||
await use(reuse);
|
||||
}, { scope: 'worker', _title: 'context' } as any],
|
||||
|
||||
|
|
|
|||
|
|
@ -92,8 +92,10 @@ export interface TestServerInterface {
|
|||
headed?: boolean;
|
||||
workers?: number | string;
|
||||
timeout?: number,
|
||||
outputDir?: string;
|
||||
reporters?: string[],
|
||||
trace?: 'on' | 'off';
|
||||
video?: 'on' | 'off';
|
||||
projects?: string[];
|
||||
reuseContext?: boolean;
|
||||
connectWsEndpoint?: string;
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
|
|||
reporter: Array.isArray(opts.reporter) ? opts.reporter : opts.reporter ? [opts.reporter] : undefined,
|
||||
workers: cliOverrides.workers,
|
||||
timeout: cliOverrides.timeout,
|
||||
outputDir: cliOverrides.outputDir,
|
||||
});
|
||||
await stopProfiling('runner');
|
||||
if (status === 'restarted')
|
||||
|
|
@ -194,6 +195,7 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
|
|||
config.cliListOnly = !!opts.list;
|
||||
config.cliProjectFilter = opts.project || undefined;
|
||||
config.cliPassWithNoTests = !!opts.passWithNoTests;
|
||||
config.cliFailOnFlakyTests = !!opts.failOnFlakyTests;
|
||||
|
||||
const runner = new Runner(config);
|
||||
let status: FullResult['status'];
|
||||
|
|
@ -337,6 +339,7 @@ const testOptions: [string, string][] = [
|
|||
['--browser <browser>', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`],
|
||||
['-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`],
|
||||
['--debug', `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --max-failures=1 --headed --workers=1" options`],
|
||||
['--fail-on-flaky-tests', `Fail if any test is flagged as flaky (default: false)`],
|
||||
['--forbid-only', `Fail if test.only is called (default: false)`],
|
||||
['--fully-parallel', `Run all tests in parallel (default: false)`],
|
||||
['--global-timeout <timeout>', `Maximum time this test suite can run in milliseconds (default: unlimited)`],
|
||||
|
|
|
|||
|
|
@ -45,27 +45,41 @@ type TestSummary = {
|
|||
fatalErrors: TestError[];
|
||||
};
|
||||
|
||||
export const isTTY = !!process.env.PWTEST_TTY_WIDTH || process.stdout.isTTY;
|
||||
export const ttyWidth = process.env.PWTEST_TTY_WIDTH ? parseInt(process.env.PWTEST_TTY_WIDTH, 10) : process.stdout.columns || 0;
|
||||
let useColors = isTTY;
|
||||
if (process.env.DEBUG_COLORS === '0'
|
||||
|| process.env.DEBUG_COLORS === 'false'
|
||||
|| process.env.FORCE_COLOR === '0'
|
||||
|| process.env.FORCE_COLOR === 'false')
|
||||
useColors = false;
|
||||
else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR)
|
||||
useColors = true;
|
||||
export const { isTTY, ttyWidth, colors } = (() => {
|
||||
let isTTY = !!process.stdout.isTTY;
|
||||
let ttyWidth = process.stdout.columns || 0;
|
||||
if (process.env.PLAYWRIGHT_FORCE_TTY === 'false' || process.env.PLAYWRIGHT_FORCE_TTY === '0') {
|
||||
isTTY = false;
|
||||
ttyWidth = 0;
|
||||
} else if (process.env.PLAYWRIGHT_FORCE_TTY === 'true' || process.env.PLAYWRIGHT_FORCE_TTY === '1') {
|
||||
isTTY = true;
|
||||
ttyWidth = process.stdout.columns || 100;
|
||||
} else if (process.env.PLAYWRIGHT_FORCE_TTY) {
|
||||
isTTY = true;
|
||||
ttyWidth = +process.env.PLAYWRIGHT_FORCE_TTY;
|
||||
if (isNaN(ttyWidth))
|
||||
ttyWidth = 100;
|
||||
}
|
||||
|
||||
export const colors = useColors ? realColors : {
|
||||
bold: (t: string) => t,
|
||||
cyan: (t: string) => t,
|
||||
dim: (t: string) => t,
|
||||
gray: (t: string) => t,
|
||||
green: (t: string) => t,
|
||||
red: (t: string) => t,
|
||||
yellow: (t: string) => t,
|
||||
enabled: false,
|
||||
};
|
||||
let useColors = isTTY;
|
||||
if (process.env.DEBUG_COLORS === '0' || process.env.DEBUG_COLORS === 'false' ||
|
||||
process.env.FORCE_COLOR === '0' || process.env.FORCE_COLOR === 'false')
|
||||
useColors = false;
|
||||
else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR)
|
||||
useColors = true;
|
||||
|
||||
const colors = useColors ? realColors : {
|
||||
bold: (t: string) => t,
|
||||
cyan: (t: string) => t,
|
||||
dim: (t: string) => t,
|
||||
gray: (t: string) => t,
|
||||
green: (t: string) => t,
|
||||
red: (t: string) => t,
|
||||
yellow: (t: string) => t,
|
||||
enabled: false,
|
||||
};
|
||||
return { isTTY, ttyWidth, colors };
|
||||
})();
|
||||
|
||||
export class BaseReporter implements ReporterV2 {
|
||||
config!: FullConfig;
|
||||
|
|
|
|||
|
|
@ -62,14 +62,14 @@ class HtmlReporter extends EmptyReporter {
|
|||
private _outputFolder!: string;
|
||||
private _attachmentsBaseURL!: string;
|
||||
private _open: string | undefined;
|
||||
private _port: number | undefined;
|
||||
private _host: string | undefined;
|
||||
private _buildResult: { ok: boolean, singleTestId: string | undefined } | undefined;
|
||||
private _topLevelErrors: TestError[] = [];
|
||||
|
||||
constructor(options: HtmlReporterOptions) {
|
||||
super();
|
||||
this._options = options;
|
||||
if (options._mode === 'test')
|
||||
process.env.PW_HTML_REPORT = '1';
|
||||
}
|
||||
|
||||
override printsToStdio() {
|
||||
|
|
@ -81,9 +81,11 @@ class HtmlReporter extends EmptyReporter {
|
|||
}
|
||||
|
||||
override onBegin(suite: Suite) {
|
||||
const { outputFolder, open, attachmentsBaseURL } = this._resolveOptions();
|
||||
const { outputFolder, open, attachmentsBaseURL, host, port } = this._resolveOptions();
|
||||
this._outputFolder = outputFolder;
|
||||
this._open = open;
|
||||
this._host = host;
|
||||
this._port = port;
|
||||
this._attachmentsBaseURL = attachmentsBaseURL;
|
||||
const reportedWarnings = new Set<string>();
|
||||
for (const project of this.config.projects) {
|
||||
|
|
@ -104,12 +106,14 @@ class HtmlReporter extends EmptyReporter {
|
|||
this.suite = suite;
|
||||
}
|
||||
|
||||
_resolveOptions(): { outputFolder: string, open: HtmlReportOpenOption, attachmentsBaseURL: string } {
|
||||
_resolveOptions(): { outputFolder: string, open: HtmlReportOpenOption, attachmentsBaseURL: string, host: string | undefined, port: number | undefined } {
|
||||
const outputFolder = reportFolderFromEnv() ?? resolveReporterOutputPath('playwright-report', this._options.configDir, this._options.outputFolder);
|
||||
return {
|
||||
outputFolder,
|
||||
open: getHtmlReportOptionProcessEnv() || this._options.open || 'on-failure',
|
||||
attachmentsBaseURL: this._options.attachmentsBaseURL || 'data/'
|
||||
attachmentsBaseURL: process.env.PLAYWRIGHT_HTML_ATTACHMENTS_BASE_URL || this._options.attachmentsBaseURL || 'data/',
|
||||
host: process.env.PLAYWRIGHT_HTML_HOST || this._options.host,
|
||||
port: process.env.PLAYWRIGHT_HTML_PORT ? +process.env.PLAYWRIGHT_HTML_PORT : this._options.port,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -135,12 +139,12 @@ class HtmlReporter extends EmptyReporter {
|
|||
const { ok, singleTestId } = this._buildResult;
|
||||
const shouldOpen = !this._options._isTestServer && (this._open === 'always' || (!ok && this._open === 'on-failure'));
|
||||
if (shouldOpen) {
|
||||
await showHTMLReport(this._outputFolder, this._options.host, this._options.port, singleTestId);
|
||||
await showHTMLReport(this._outputFolder, this._host, this._port, singleTestId);
|
||||
} else if (this._options._mode === 'test') {
|
||||
const packageManagerCommand = getPackageManagerExecCommand();
|
||||
const relativeReportPath = this._outputFolder === standaloneDefaultFolder() ? '' : ' ' + path.relative(process.cwd(), this._outputFolder);
|
||||
const hostArg = this._options.host ? ` --host ${this._options.host}` : '';
|
||||
const portArg = this._options.port ? ` --port ${this._options.port}` : '';
|
||||
const hostArg = this._host ? ` --host ${this._host}` : '';
|
||||
const portArg = this._port ? ` --port ${this._port}` : '';
|
||||
console.log('');
|
||||
console.log('To open last HTML report run:');
|
||||
console.log(colors.cyan(`
|
||||
|
|
@ -151,18 +155,18 @@ class HtmlReporter extends EmptyReporter {
|
|||
}
|
||||
|
||||
function reportFolderFromEnv(): string | undefined {
|
||||
if (process.env[`PLAYWRIGHT_HTML_REPORT`])
|
||||
return path.resolve(process.cwd(), process.env[`PLAYWRIGHT_HTML_REPORT`]);
|
||||
return undefined;
|
||||
// Note: PLAYWRIGHT_HTML_REPORT is for backwards compatibility.
|
||||
const envValue = process.env.PLAYWRIGHT_HTML_OUTPUT_DIR || process.env.PLAYWRIGHT_HTML_REPORT;
|
||||
return envValue ? path.resolve(envValue) : undefined;
|
||||
}
|
||||
|
||||
function getHtmlReportOptionProcessEnv(): HtmlReportOpenOption | undefined {
|
||||
const processKey = 'PW_TEST_HTML_REPORT_OPEN';
|
||||
const htmlOpenEnv = process.env[processKey];
|
||||
// Note: PW_TEST_HTML_REPORT_OPEN is for backwards compatibility.
|
||||
const htmlOpenEnv = process.env.PLAYWRIGHT_HTML_OPEN || process.env.PW_TEST_HTML_REPORT_OPEN;
|
||||
if (!htmlOpenEnv)
|
||||
return undefined;
|
||||
if (!isHtmlReportOption(htmlOpenEnv)) {
|
||||
console.log(colors.red(`Configuration Error: HTML reporter Invalid value for ${processKey}: ${htmlOpenEnv}. Valid values are: ${htmlReportOptions.join(', ')}`));
|
||||
console.log(colors.red(`Configuration Error: HTML reporter Invalid value for PLAYWRIGHT_HTML_OPEN: ${htmlOpenEnv}. Valid values are: ${htmlReportOptions.join(', ')}`));
|
||||
return undefined;
|
||||
}
|
||||
return htmlOpenEnv;
|
||||
|
|
@ -182,7 +186,8 @@ export async function showHTMLReport(reportFolder: string | undefined, host: str
|
|||
return;
|
||||
}
|
||||
const server = startHtmlReportServer(folder);
|
||||
let url = await server.start({ port, host, preferredPort: port ? undefined : 9323 });
|
||||
await server.start({ port, host, preferredPort: port ? undefined : 9323 });
|
||||
let url = server.urlPrefix('human-readable');
|
||||
console.log('');
|
||||
console.log(colors.cyan(` Serving HTML report at ${url}. Press Ctrl+C to quit.`));
|
||||
if (testId)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import path from 'path';
|
|||
import type { FullConfig, FullResult, Suite, TestCase } from '../../types/testReporter';
|
||||
import { formatFailure, resolveOutputFile, stripAnsiEscapes } from './base';
|
||||
import EmptyReporter from './empty';
|
||||
import { getAsBooleanFromENV } from 'playwright-core/lib/utils';
|
||||
|
||||
type JUnitOptions = {
|
||||
outputFile?: string,
|
||||
|
|
@ -42,8 +43,8 @@ class JUnitReporter extends EmptyReporter {
|
|||
|
||||
constructor(options: JUnitOptions) {
|
||||
super();
|
||||
this.stripANSIControlSequences = options.stripANSIControlSequences || false;
|
||||
this.includeProjectInTestName = options.includeProjectInTestName || false;
|
||||
this.stripANSIControlSequences = getAsBooleanFromENV('PLAYWRIGHT_JUNIT_STRIP_ANSI', !!options.stripANSIControlSequences);
|
||||
this.includeProjectInTestName = getAsBooleanFromENV('PLAYWRIGHT_JUNIT_INCLUDE_PROJECT_IN_TEST_NAME', !!options.includeProjectInTestName);
|
||||
this.configDir = options.configDir;
|
||||
this.resolvedOutputFile = resolveOutputFile('JUNIT', options)?.outputFile;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
import { ms as milliseconds } from 'playwright-core/lib/utilsBundle';
|
||||
import { colors, BaseReporter, formatError, formatTestTitle, isTTY, stepSuffix, stripAnsiEscapes, ttyWidth } from './base';
|
||||
import type { FullResult, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter';
|
||||
import { getAsBooleanFromENV } from 'playwright-core/lib/utils';
|
||||
|
||||
// Allow it in the Visual Studio Code Terminal and the new Windows Terminal
|
||||
const DOES_NOT_SUPPORT_UTF8_IN_TERMINAL = process.platform === 'win32' && process.env.TERM_PROGRAM !== 'vscode' && !process.env.WT_SESSION;
|
||||
|
|
@ -33,9 +34,9 @@ class ListReporter extends BaseReporter {
|
|||
private _needNewLine = false;
|
||||
private _printSteps: boolean;
|
||||
|
||||
constructor(options: { omitFailures?: boolean, printSteps?: boolean } = {}) {
|
||||
super(options);
|
||||
this._printSteps = isTTY && (options.printSteps || !!process.env.PW_TEST_DEBUG_REPORTERS_PRINT_STEPS);
|
||||
constructor(options: { printSteps?: boolean } = {}) {
|
||||
super();
|
||||
this._printSteps = isTTY && getAsBooleanFromENV('PLAYWRIGHT_LIST_PRINT_STEPS', options.printSteps);
|
||||
}
|
||||
|
||||
override printsToStdio() {
|
||||
|
|
@ -229,8 +230,6 @@ class ListReporter extends BaseReporter {
|
|||
// Go down if needed.
|
||||
if (row !== this._lastRow)
|
||||
process.stdout.write(`\u001B[${this._lastRow - row}E`);
|
||||
if (process.env.PWTEST_TTY_WIDTH)
|
||||
process.stdout.write('\n'); // For testing.
|
||||
}
|
||||
|
||||
private _testPrefix(index: string, statusMark: string) {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,15 @@ export class FailureTracker {
|
|||
}
|
||||
|
||||
result(): 'failed' | 'passed' {
|
||||
return this._hasWorkerErrors || this.hasReachedMaxFailures() || this._rootSuite?.allTests().some(test => !test.ok()) ? 'failed' : 'passed';
|
||||
return this._hasWorkerErrors || this.hasReachedMaxFailures() || this.hasFailedTests() || (this._config.cliFailOnFlakyTests && this.hasFlakyTests()) ? 'failed' : 'passed';
|
||||
}
|
||||
|
||||
hasFailedTests() {
|
||||
return this._rootSuite?.allTests().some(test => !test.ok());
|
||||
}
|
||||
|
||||
hasFlakyTests() {
|
||||
return this._rootSuite?.allTests().some(test => (test.outcome() === 'flaky'));
|
||||
}
|
||||
|
||||
maxFailures() {
|
||||
|
|
|
|||
|
|
@ -308,10 +308,12 @@ class TestServerDispatcher implements TestServerInterface {
|
|||
reporter: params.reporters ? params.reporters.map(r => [r]) : undefined,
|
||||
use: {
|
||||
trace: params.trace === 'on' ? { mode: 'on', sources: false, _live: true } : (params.trace === 'off' ? 'off' : undefined),
|
||||
video: params.video === 'on' ? 'on' : (params.video === 'off' ? 'off' : undefined),
|
||||
headless: params.headed ? false : undefined,
|
||||
_optionContextReuseMode: params.reuseContext ? 'when-possible' : undefined,
|
||||
_optionConnectOptions: params.connectWsEndpoint ? { wsEndpoint: params.connectWsEndpoint } : undefined,
|
||||
},
|
||||
outputDir: params.outputDir,
|
||||
workers: params.workers,
|
||||
};
|
||||
if (params.trace === 'on')
|
||||
|
|
@ -417,9 +419,9 @@ export async function runUIMode(configFile: string | undefined, options: TraceVi
|
|||
return await innerRunTestServer(configLocation, options, async (server: HttpServer, cancelPromise: ManualPromise<void>) => {
|
||||
await installRootRedirect(server, [], { ...options, webApp: 'uiMode.html' });
|
||||
if (options.host !== undefined || options.port !== undefined) {
|
||||
await openTraceInBrowser(server.urlPrefix());
|
||||
await openTraceInBrowser(server.urlPrefix('human-readable'));
|
||||
} else {
|
||||
const page = await openTraceViewerApp(server.urlPrefix(), 'chromium', {
|
||||
const page = await openTraceViewerApp(server.urlPrefix('precise'), 'chromium', {
|
||||
headless: isUnderTest() && process.env.PWTEST_HEADED_FOR_TEST !== '1',
|
||||
persistentContextOptions: {
|
||||
handleSIGINT: false,
|
||||
|
|
@ -434,7 +436,7 @@ export async function runTestServer(configFile: string | undefined, options: { h
|
|||
const configLocation = resolveConfigLocation(configFile);
|
||||
return await innerRunTestServer(configLocation, options, async server => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Listening on ' + server.urlPrefix().replace('http:', 'ws:') + '/' + server.wsGuid());
|
||||
console.log('Listening on ' + server.urlPrefix('precise').replace('http:', 'ws:') + '/' + server.wsGuid());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,12 +100,17 @@ export class WorkerMain extends ProcessRunner {
|
|||
override async gracefullyClose() {
|
||||
try {
|
||||
await this._stop();
|
||||
// Ignore top-level errors, they are already inside TestInfo.errors.
|
||||
const fakeTestInfo = new TestInfoImpl(this._config, this._project, this._params, undefined, 0, () => {}, () => {}, () => {});
|
||||
const runnable = { type: 'teardown' } as const;
|
||||
// We have to load the project to get the right deadline below.
|
||||
await this._loadIfNeeded();
|
||||
await this._teardownScopes();
|
||||
await fakeTestInfo._runAsStage({ title: 'worker cleanup', runnable }, () => this._loadIfNeeded()).catch(() => {});
|
||||
await this._fixtureRunner.teardownScope('test', fakeTestInfo, runnable).catch(() => {});
|
||||
await this._fixtureRunner.teardownScope('worker', fakeTestInfo, runnable).catch(() => {});
|
||||
// Close any other browsers launched in this process. This includes anything launched
|
||||
// manually in the test/hooks and internal browsers like Playwright Inspector.
|
||||
await gracefullyCloseAll();
|
||||
await fakeTestInfo._runAsStage({ title: 'worker cleanup', runnable }, () => gracefullyCloseAll()).catch(() => {});
|
||||
this._fatalErrors.push(...fakeTestInfo.errors);
|
||||
} catch (e) {
|
||||
this._fatalErrors.push(serializeError(e));
|
||||
}
|
||||
|
|
@ -144,15 +149,6 @@ export class WorkerMain extends ProcessRunner {
|
|||
}
|
||||
}
|
||||
|
||||
private async _teardownScopes() {
|
||||
const fakeTestInfo = new TestInfoImpl(this._config, this._project, this._params, undefined, 0, () => {}, () => {}, () => {});
|
||||
const runnable = { type: 'teardown' } as const;
|
||||
// Ignore top-level errors, they are already inside TestInfo.errors.
|
||||
await this._fixtureRunner.teardownScope('test', fakeTestInfo, runnable).catch(() => {});
|
||||
await this._fixtureRunner.teardownScope('worker', fakeTestInfo, runnable).catch(() => {});
|
||||
this._fatalErrors.push(...fakeTestInfo.errors);
|
||||
}
|
||||
|
||||
unhandledError(error: Error | any) {
|
||||
// No current test - fatal error.
|
||||
if (!this._currentTest) {
|
||||
|
|
|
|||
6
packages/playwright/types/test.d.ts
vendored
6
packages/playwright/types/test.d.ts
vendored
|
|
@ -2184,7 +2184,8 @@ interface TestFunction<TestArgs> {
|
|||
* test('basic test', {
|
||||
* annotation: {
|
||||
* type: 'issue',
|
||||
* description: 'https://github.com/microsoft/playwright/issues/23180',
|
||||
* description: 'feature tags API',
|
||||
* url: 'https://github.com/microsoft/playwright/issues/23180'
|
||||
* },
|
||||
* }, async ({ page }) => {
|
||||
* await page.goto('https://playwright.dev/');
|
||||
|
|
@ -2260,7 +2261,8 @@ interface TestFunction<TestArgs> {
|
|||
* test('basic test', {
|
||||
* annotation: {
|
||||
* type: 'issue',
|
||||
* description: 'https://github.com/microsoft/playwright/issues/23180',
|
||||
* description: 'feature tags API',
|
||||
* url: 'https://github.com/microsoft/playwright/issues/23180'
|
||||
* },
|
||||
* }, async ({ page }) => {
|
||||
* await page.goto('https://playwright.dev/');
|
||||
|
|
|
|||
|
|
@ -157,6 +157,8 @@ function indexModel(context: ContextEntry) {
|
|||
}
|
||||
for (const event of context.events)
|
||||
(event as any)[contextSymbol] = context;
|
||||
for (const resource of context.resources)
|
||||
(resource as any)[contextSymbol] = context;
|
||||
}
|
||||
|
||||
function mergeActionsAndUpdateTiming(contexts: ContextEntry[]) {
|
||||
|
|
@ -330,7 +332,7 @@ export function idForAction(action: ActionTraceEvent) {
|
|||
return `${action.pageId || 'none'}:${action.callId}`;
|
||||
}
|
||||
|
||||
export function context(action: ActionTraceEvent | trace.EventTraceEvent): ContextEntry {
|
||||
export function context(action: ActionTraceEvent | trace.EventTraceEvent | ResourceSnapshot): ContextEntry {
|
||||
return (action as any)[contextSymbol];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,12 +21,14 @@ import './networkTab.css';
|
|||
import { NetworkResourceDetails } from './networkResourceDetails';
|
||||
import { bytesToString, msToString } from '@web/uiUtils';
|
||||
import { PlaceholderPanel } from './placeholderPanel';
|
||||
import type { MultiTraceModel } from './modelUtil';
|
||||
import { context, type MultiTraceModel } from './modelUtil';
|
||||
import { GridView, type RenderedGridCell } from '@web/components/gridView';
|
||||
import { SplitView } from '@web/components/splitView';
|
||||
import type { ContextEntry } from '../entries';
|
||||
|
||||
type NetworkTabModel = {
|
||||
resources: Entry[],
|
||||
contextIdMap: ContextIdMap,
|
||||
};
|
||||
|
||||
type RenderedEntry = {
|
||||
|
|
@ -39,6 +41,7 @@ type RenderedEntry = {
|
|||
start: number,
|
||||
route: string,
|
||||
resource: Entry,
|
||||
contextId: string,
|
||||
};
|
||||
type ColumnName = keyof RenderedEntry;
|
||||
type Sorting = { by: ColumnName, negate: boolean};
|
||||
|
|
@ -54,7 +57,8 @@ export function useNetworkTabModel(model: MultiTraceModel | undefined, selectedT
|
|||
});
|
||||
return filtered;
|
||||
}, [model, selectedTime]);
|
||||
return { resources };
|
||||
const contextIdMap = React.useMemo(() => new ContextIdMap(model), [model]);
|
||||
return { resources, contextIdMap };
|
||||
}
|
||||
|
||||
export const NetworkTab: React.FunctionComponent<{
|
||||
|
|
@ -66,11 +70,11 @@ export const NetworkTab: React.FunctionComponent<{
|
|||
const [selectedEntry, setSelectedEntry] = React.useState<RenderedEntry | undefined>(undefined);
|
||||
|
||||
const { renderedEntries } = React.useMemo(() => {
|
||||
const renderedEntries = networkModel.resources.map(entry => renderEntry(entry, boundaries));
|
||||
const renderedEntries = networkModel.resources.map(entry => renderEntry(entry, boundaries, networkModel.contextIdMap));
|
||||
if (sorting)
|
||||
sort(renderedEntries, sorting);
|
||||
return { renderedEntries };
|
||||
}, [networkModel.resources, sorting, boundaries]);
|
||||
}, [networkModel.resources, networkModel.contextIdMap, sorting, boundaries]);
|
||||
|
||||
if (!networkModel.resources.length)
|
||||
return <PlaceholderPanel text='No network calls' />;
|
||||
|
|
@ -81,7 +85,7 @@ export const NetworkTab: React.FunctionComponent<{
|
|||
selectedItem={selectedEntry}
|
||||
onSelected={item => setSelectedEntry(item)}
|
||||
onHighlighted={item => onEntryHovered(item?.resource)}
|
||||
columns={selectedEntry ? ['name'] : ['name', 'method', 'status', 'contentType', 'duration', 'size', 'start', 'route']}
|
||||
columns={visibleColumns(!!selectedEntry, renderedEntries)}
|
||||
columnTitle={columnTitle}
|
||||
columnWidth={columnWidth}
|
||||
isError={item => item.status.code >= 400}
|
||||
|
|
@ -100,6 +104,8 @@ export const NetworkTab: React.FunctionComponent<{
|
|||
};
|
||||
|
||||
const columnTitle = (column: ColumnName) => {
|
||||
if (column === 'contextId')
|
||||
return 'Source';
|
||||
if (column === 'name')
|
||||
return 'Name';
|
||||
if (column === 'method')
|
||||
|
|
@ -128,10 +134,28 @@ const columnWidth = (column: ColumnName) => {
|
|||
return 60;
|
||||
if (column === 'contentType')
|
||||
return 200;
|
||||
if (column === 'contextId')
|
||||
return 60;
|
||||
return 100;
|
||||
};
|
||||
|
||||
function visibleColumns(entrySelected: boolean, renderedEntries: RenderedEntry[]): (keyof RenderedEntry)[] {
|
||||
if (entrySelected)
|
||||
return ['name'];
|
||||
const columns: (keyof RenderedEntry)[] = [];
|
||||
if (hasMultipleContexts(renderedEntries))
|
||||
columns.push('contextId');
|
||||
columns.push('name', 'method', 'status', 'contentType', 'duration', 'size', 'start', 'route');
|
||||
return columns;
|
||||
}
|
||||
|
||||
const renderCell = (entry: RenderedEntry, column: ColumnName): RenderedGridCell => {
|
||||
if (column === 'contextId') {
|
||||
return {
|
||||
body: entry.contextId,
|
||||
title: entry.name.url,
|
||||
};
|
||||
}
|
||||
if (column === 'name') {
|
||||
return {
|
||||
body: entry.name.name,
|
||||
|
|
@ -159,7 +183,57 @@ const renderCell = (entry: RenderedEntry, column: ColumnName): RenderedGridCell
|
|||
return { body: '' };
|
||||
};
|
||||
|
||||
const renderEntry = (resource: Entry, boundaries: Boundaries): RenderedEntry => {
|
||||
class ContextIdMap {
|
||||
private _pagerefToShortId = new Map<string, string>();
|
||||
private _contextToId = new Map<ContextEntry, string>();
|
||||
private _lastPageId = 0;
|
||||
private _lastApiRequestContextId = 0;
|
||||
|
||||
constructor(model: MultiTraceModel | undefined) {}
|
||||
|
||||
contextId(resource: Entry): string {
|
||||
if (resource.pageref)
|
||||
return this._pageId(resource.pageref);
|
||||
else if (resource._apiRequest)
|
||||
return this._apiRequestContextId(resource);
|
||||
return '';
|
||||
}
|
||||
|
||||
private _pageId(pageref: string): string {
|
||||
let shortId = this._pagerefToShortId.get(pageref);
|
||||
if (!shortId) {
|
||||
++this._lastPageId;
|
||||
shortId = 'page#' + this._lastPageId;
|
||||
this._pagerefToShortId.set(pageref, shortId);
|
||||
}
|
||||
return shortId;
|
||||
}
|
||||
|
||||
private _apiRequestContextId(resource: Entry): string {
|
||||
const contextEntry = context(resource);
|
||||
if (!contextEntry)
|
||||
return '';
|
||||
let contextId = this._contextToId.get(contextEntry);
|
||||
if (!contextId) {
|
||||
++this._lastApiRequestContextId;
|
||||
contextId = 'api#' + this._lastApiRequestContextId;
|
||||
this._contextToId.set(contextEntry, contextId);
|
||||
}
|
||||
return contextId;
|
||||
}
|
||||
}
|
||||
|
||||
function hasMultipleContexts(renderedEntries: RenderedEntry[]): boolean {
|
||||
const contextIds = new Set<string>();
|
||||
for (const entry of renderedEntries) {
|
||||
contextIds.add(entry.contextId);
|
||||
if (contextIds.size > 1)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const renderEntry = (resource: Entry, boundaries: Boundaries, contextIdGenerator: ContextIdMap): RenderedEntry => {
|
||||
const routeStatus = formatRouteStatus(resource);
|
||||
let resourceName: string;
|
||||
try {
|
||||
|
|
@ -184,7 +258,8 @@ const renderEntry = (resource: Entry, boundaries: Boundaries): RenderedEntry =>
|
|||
size: resource.response._transferSize! > 0 ? resource.response._transferSize! : resource.response.bodySize,
|
||||
start: resource._monotonicTime! - boundaries.minimum,
|
||||
route: routeStatus,
|
||||
resource
|
||||
resource,
|
||||
contextId: contextIdGenerator.contextId(resource),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -249,4 +324,7 @@ function comparator(sortBy: ColumnName) {
|
|||
return a.route.localeCompare(b.route);
|
||||
};
|
||||
}
|
||||
|
||||
if (sortBy === 'contextId')
|
||||
return (a: RenderedEntry, b: RenderedEntry) => a.contextId.localeCompare(b.contextId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ const queryParams = {
|
|||
workers: searchParams.get('workers') || undefined,
|
||||
timeout: searchParams.has('timeout') ? +searchParams.get('timeout')! : undefined,
|
||||
headed: searchParams.has('headed'),
|
||||
outputDir: searchParams.get('outputDir') || undefined,
|
||||
reporters: searchParams.has('reporter') ? searchParams.getAll('reporter') : undefined,
|
||||
};
|
||||
|
||||
|
|
@ -283,6 +284,7 @@ export const UIModeView: React.FC<{}> = ({
|
|||
workers: queryParams.workers,
|
||||
timeout: queryParams.timeout,
|
||||
headed: queryParams.headed,
|
||||
outputDir: queryParams.outputDir,
|
||||
reporters: queryParams.reporters,
|
||||
trace: 'on',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export function useMeasure<T extends Element>() {
|
|||
}
|
||||
|
||||
export function msToString(ms: number): string {
|
||||
if (!isFinite(ms))
|
||||
if (ms < 0 || !isFinite(ms))
|
||||
return '-';
|
||||
|
||||
if (ms === 0)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
import { test } from './npmTest';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
test('electron should work', async ({ exec, tsc, writeFiles }) => {
|
||||
await exec('npm i playwright electron@19.0.11');
|
||||
|
|
@ -24,3 +26,16 @@ test('electron should work', async ({ exec, tsc, writeFiles }) => {
|
|||
});
|
||||
await tsc('test.ts');
|
||||
});
|
||||
|
||||
test('electron should work with special characters in path', async ({ exec, tmpWorkspace }) => {
|
||||
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30755' });
|
||||
const folderName = path.join(tmpWorkspace, '!@#$% тест with spaces and 😊');
|
||||
|
||||
await exec('npm i playwright electron@19.0.11');
|
||||
await fs.promises.mkdir(folderName);
|
||||
for (const file of ['electron-app.js', 'sanity-electron.js'])
|
||||
await fs.promises.copyFile(path.join(tmpWorkspace, file), path.join(folderName, file));
|
||||
await exec('node sanity-electron.js', {
|
||||
cwd: path.join(folderName)
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -198,6 +198,20 @@ it('should follow redirects', async ({ context, server }) => {
|
|||
expect(await response.json()).toEqual({ foo: 'bar' });
|
||||
});
|
||||
|
||||
it('should follow redirects correctly when Location header contains UTF-8 characters', async ({ context, server }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30903' });
|
||||
server.setRoute('/redirect', (req, res) => {
|
||||
// Node.js only allows US-ASCII, so we can't send invalid headers directly. Sending it as a raw response instead.
|
||||
res.socket.write('HTTP/1.1 301 Moved Permanently\r\n');
|
||||
res.socket.write(`Location: ${server.PREFIX}/empty.html?message=マスクПривет\r\n`);
|
||||
res.socket.write('\r\n');
|
||||
res.socket.uncork();
|
||||
res.socket.end();
|
||||
});
|
||||
const response = await context.request.get(server.PREFIX + '/redirect');
|
||||
expect(response.url()).toBe(server.PREFIX + '/empty.html?' + new URLSearchParams({ message: 'マスクПривет' }));
|
||||
});
|
||||
|
||||
it('should add cookies from Set-Cookie header', async ({ context, page, server }) => {
|
||||
server.setRoute('/setcookie.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', ['session=value', 'foo=bar; max-age=3600']);
|
||||
|
|
@ -794,21 +808,6 @@ it('should respect timeout after redirects', async function({ context, server })
|
|||
expect(error.message).toContain(`Request timed out after 100ms`);
|
||||
});
|
||||
|
||||
it('should throw on a redirect with an invalid URL', async ({ context, server }) => {
|
||||
server.setRedirect('/redirect', '/test');
|
||||
server.setRoute('/test', (req, res) => {
|
||||
// Node.js prevents us from responding with an invalid header, therefore we manually write the response.
|
||||
const conn = res.connection!;
|
||||
conn.write('HTTP/1.1 302\r\n');
|
||||
conn.write('Location: https://здравствуйте/\r\n');
|
||||
conn.write('\r\n');
|
||||
conn.uncork();
|
||||
conn.end();
|
||||
});
|
||||
const error = await context.request.get(server.PREFIX + '/redirect').catch(e => e);
|
||||
expect(error.message).toContain('apiRequestContext.get: uri requested responds with an invalid redirect URL');
|
||||
});
|
||||
|
||||
it('should not hang on a brotli encoded Range request', async ({ context, server }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/18190' });
|
||||
it.skip(+process.versions.node.split('.')[0] < 18);
|
||||
|
|
@ -1256,3 +1255,8 @@ it('should not work after dispose', async ({ context, server }) => {
|
|||
await context.request.dispose();
|
||||
expect(await context.request.get(server.EMPTY_PAGE).catch(e => e.message)).toContain(kTargetClosedErrorMessage);
|
||||
});
|
||||
|
||||
it('should not work after context dispose', async ({ context, server }) => {
|
||||
await context.close({ reason: 'Test ended.' });
|
||||
expect(await context.request.get(server.EMPTY_PAGE).catch(e => e.message)).toContain('Test ended.');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ it('SharedArrayBuffer should work @smoke', async function({ contextFactory, http
|
|||
expect(await page.evaluate(() => typeof SharedArrayBuffer)).toBe('function');
|
||||
});
|
||||
|
||||
it('Web Assembly should work @smoke', async function({ page, server }) {
|
||||
it('Web Assembly should work @smoke', async ({ page, server, browserName, platform }) => {
|
||||
it.fixme(browserName === 'webkit' && platform === 'win32', 'Windows JIT is disabled: https://bugs.webkit.org/show_bug.cgi?id=273854');
|
||||
await page.goto(server.PREFIX + '/wasm/table2.html');
|
||||
expect(await page.evaluate('loadTable()')).toBe('42, 83');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,11 +19,9 @@ import { test as it, expect } from './pageTest';
|
|||
import { attachFrame } from '../config/utils';
|
||||
import fs from 'fs';
|
||||
|
||||
function adjustServerHeaders(headers: Object, browserName: string, channel: string) {
|
||||
if (browserName === 'firefox' && channel === 'firefox-beta') {
|
||||
// This is a new experimental feature, only enabled in Firefox Beta for now.
|
||||
function adjustServerHeaders(headers: Object, browserName: string) {
|
||||
if (browserName === 'firefox')
|
||||
delete headers['priority'];
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +88,7 @@ it('should return headers', async ({ page, server, browserName }) => {
|
|||
expect(response.request().headers()['user-agent']).toContain('WebKit');
|
||||
});
|
||||
|
||||
it('should get the same headers as the server', async ({ page, server, browserName, platform, isElectron, browserMajorVersion, channel }) => {
|
||||
it('should get the same headers as the server', async ({ page, server, browserName, platform, isElectron, browserMajorVersion }) => {
|
||||
it.skip(isElectron && browserMajorVersion < 99, 'This needs Chromium >= 99');
|
||||
it.fail(browserName === 'webkit' && platform === 'win32', 'Curl does not show accept-encoding and accept-language');
|
||||
let serverRequest;
|
||||
|
|
@ -100,10 +98,10 @@ it('should get the same headers as the server', async ({ page, server, browserNa
|
|||
});
|
||||
const response = await page.goto(server.PREFIX + '/empty.html');
|
||||
const headers = await response.request().allHeaders();
|
||||
expect(headers).toEqual(adjustServerHeaders(serverRequest.headers, browserName, channel));
|
||||
expect(headers).toEqual(adjustServerHeaders(serverRequest.headers, browserName));
|
||||
});
|
||||
|
||||
it('should not return allHeaders() until they are available', async ({ page, server, browserName, platform, isElectron, browserMajorVersion, channel }) => {
|
||||
it('should not return allHeaders() until they are available', async ({ page, server, browserName, platform, isElectron, browserMajorVersion }) => {
|
||||
it.skip(isElectron && browserMajorVersion < 99, 'This needs Chromium >= 99');
|
||||
it.fail(browserName === 'webkit' && platform === 'win32', 'Curl does not show accept-encoding and accept-language');
|
||||
|
||||
|
|
@ -122,13 +120,13 @@ it('should not return allHeaders() until they are available', async ({ page, ser
|
|||
|
||||
await page.goto(server.PREFIX + '/empty.html');
|
||||
const requestHeaders = await requestHeadersPromise;
|
||||
expect(requestHeaders).toEqual(adjustServerHeaders(serverRequest.headers, browserName, channel));
|
||||
expect(requestHeaders).toEqual(adjustServerHeaders(serverRequest.headers, browserName));
|
||||
|
||||
const responseHeaders = await responseHeadersPromise;
|
||||
expect(responseHeaders['foo']).toBe('bar');
|
||||
});
|
||||
|
||||
it('should get the same headers as the server CORS', async ({ page, server, browserName, platform, isElectron, browserMajorVersion, channel }) => {
|
||||
it('should get the same headers as the server CORS', async ({ page, server, browserName, platform, isElectron, browserMajorVersion, }) => {
|
||||
it.skip(isElectron && browserMajorVersion < 99, 'This needs Chromium >= 99');
|
||||
it.fail(browserName === 'webkit' && platform === 'win32', 'Curl does not show accept-encoding and accept-language');
|
||||
|
||||
|
|
@ -147,7 +145,7 @@ it('should get the same headers as the server CORS', async ({ page, server, brow
|
|||
expect(text).toBe('done');
|
||||
const response = await responsePromise;
|
||||
const headers = await response.request().allHeaders();
|
||||
expect(headers).toEqual(adjustServerHeaders(serverRequest.headers, browserName, channel));
|
||||
expect(headers).toEqual(adjustServerHeaders(serverRequest.headers, browserName));
|
||||
});
|
||||
|
||||
it('should not get preflight CORS requests when intercepting', async ({ page, server, browserName, isAndroid }) => {
|
||||
|
|
@ -408,10 +406,9 @@ it('should report raw headers', async ({ page, server, browserName, platform, is
|
|||
return { name, value: values[0] };
|
||||
});
|
||||
}
|
||||
if (browserName === 'firefox' && channel === 'firefox-beta') {
|
||||
// This is a new experimental feature, only enabled in Firefox Beta for now.
|
||||
if (browserName === 'firefox')
|
||||
expectedHeaders = expectedHeaders.filter(({ name }) => name.toLowerCase() !== 'priority');
|
||||
}
|
||||
|
||||
res.end();
|
||||
});
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
|
|
|
|||
|
|
@ -477,3 +477,46 @@ it('should intercept css variable with background url', async ({ page, server })
|
|||
await page.waitForTimeout(1000);
|
||||
expect(interceptedRequests).toBe(1);
|
||||
});
|
||||
|
||||
it('continue should not change multipart/form-data body', async ({ page, server, browserName }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/19158' });
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
server.setRoute('/upload', (request, response) => {
|
||||
response.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
response.end('done');
|
||||
});
|
||||
async function sendFormData() {
|
||||
const reqPromise = server.waitForRequest('/upload');
|
||||
const status = await page.evaluate(async () => {
|
||||
const newFile = new File(['file content'], 'file.txt');
|
||||
const formData = new FormData();
|
||||
formData.append('file', newFile);
|
||||
const response = await fetch('/upload', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
body: formData,
|
||||
});
|
||||
return response.status;
|
||||
});
|
||||
const req = await reqPromise;
|
||||
expect(status).toBe(200);
|
||||
return req;
|
||||
}
|
||||
const reqBefore = await sendFormData();
|
||||
await page.route('**/*', async route => {
|
||||
await route.continue();
|
||||
});
|
||||
const reqAfter = await sendFormData();
|
||||
const fileContent = [
|
||||
'Content-Disposition: form-data; name=\"file\"; filename=\"file.txt\"',
|
||||
'Content-Type: application/octet-stream',
|
||||
'',
|
||||
'file content',
|
||||
'------'].join('\r\n');
|
||||
expect.soft((await reqBefore.postBody).toString('utf8')).toContain(fileContent);
|
||||
expect.soft((await reqAfter.postBody).toString('utf8')).toContain(fileContent);
|
||||
// Firefox sends a bit longer boundary.
|
||||
const expectedLength = browserName === 'firefox' ? '246' : '208';
|
||||
expect.soft(reqBefore.headers['content-length']).toBe(expectedLength);
|
||||
expect.soft(reqAfter.headers['content-length']).toBe(expectedLength);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -78,8 +78,9 @@ it('should work with status code 422', async ({ page, server }) => {
|
|||
expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!');
|
||||
});
|
||||
|
||||
it('should throw exception if status code is not supported', async ({ page, server, browserName }) => {
|
||||
it('should fulfill with unuassigned status codes', async ({ page, server, browserName }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28490' });
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30773' });
|
||||
let fulfillPromiseCallback;
|
||||
const fulfillPromise = new Promise<Error|undefined>(f => fulfillPromiseCallback = f);
|
||||
await page.route('**/data.json', route => {
|
||||
|
|
@ -89,14 +90,14 @@ it('should throw exception if status code is not supported', async ({ page, serv
|
|||
}).catch(e => e));
|
||||
});
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
page.evaluate(url => fetch(url), server.PREFIX + '/data.json').catch(() => {});
|
||||
const response = await page.evaluate(async url => {
|
||||
const { status, statusText } = await fetch(url);
|
||||
return { status, statusText };
|
||||
}, server.PREFIX + '/data.json');
|
||||
const error = await fulfillPromise;
|
||||
if (browserName === 'chromium') {
|
||||
expect(error).toBeTruthy();
|
||||
expect(error.message).toContain(' Invalid http status code or phrase');
|
||||
} else {
|
||||
expect(error).toBe(undefined);
|
||||
}
|
||||
expect(error).toBe(undefined);
|
||||
expect(response.status).toBe(430);
|
||||
expect(response.statusText).toBe('Unknown');
|
||||
});
|
||||
|
||||
it('should not throw if request was cancelled by the page', async ({ page, server }) => {
|
||||
|
|
@ -456,3 +457,30 @@ it('should fulfill with gzip and readback', {
|
|||
await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)');
|
||||
expect(await response.text()).toContain(`<div>hello, world!</div>`);
|
||||
});
|
||||
|
||||
it('should not go to the network for fulfilled requests body', {
|
||||
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30760' },
|
||||
}, async ({ page, server, browserName }) => {
|
||||
await page.route('**/one-style.css', async route => {
|
||||
return route.fulfill({
|
||||
status: 404,
|
||||
contentType: 'text/plain',
|
||||
body: 'Not Found! (mocked)',
|
||||
});
|
||||
});
|
||||
|
||||
let serverHit = false;
|
||||
server.setRoute('/one-style.css', (req, res) => {
|
||||
serverHit = true;
|
||||
res.setHeader('Content-Type', 'text/css');
|
||||
res.end('body { background-color: green; }');
|
||||
});
|
||||
|
||||
const responsePromise = page.waitForResponse('**/one-style.css');
|
||||
await page.goto(server.PREFIX + '/one-style.html');
|
||||
const response = await responsePromise;
|
||||
const body = await response.body();
|
||||
expect(body).toBeTruthy();
|
||||
expect(serverHit).toBe(false);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -128,6 +128,19 @@ test('should allow flaky', async ({ runInlineTest }) => {
|
|||
expect(result.flaky).toBe(1);
|
||||
});
|
||||
|
||||
test('failOnFlakyTests flag disallows flaky', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.js': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('flake', async ({}, testInfo) => {
|
||||
expect(testInfo.retry).toBe(1);
|
||||
});
|
||||
`,
|
||||
}, { 'retries': 1, 'fail-on-flaky-tests': true });
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
expect(result.flaky).toBe(1);
|
||||
});
|
||||
|
||||
test('should fail on unexpected pass', async ({ runInlineTest }) => {
|
||||
const { exitCode, failed, output } = await runInlineTest({
|
||||
'unexpected-pass.spec.js': `
|
||||
|
|
|
|||
|
|
@ -224,6 +224,7 @@ export function cleanEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|||
GITHUB_SHA: undefined,
|
||||
// END: Reserved CI
|
||||
PW_TEST_HTML_REPORT_OPEN: undefined,
|
||||
PLAYWRIGHT_HTML_OPEN: undefined,
|
||||
PW_TEST_REPORTER: undefined,
|
||||
PW_TEST_REPORTER_WS_ENDPOINT: undefined,
|
||||
PW_TEST_SOURCE_TRANSFORM: undefined,
|
||||
|
|
|
|||
|
|
@ -85,35 +85,6 @@ test('should not reuse context with video if mode=when-possible', async ({ runIn
|
|||
expect(fs.existsSync(testInfo.outputPath('test-results', 'reuse-two', 'video.webm'))).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should reuse context and disable video if mode=force', async ({ runInlineTest }, testInfo) => {
|
||||
const result = await runInlineTest({
|
||||
'playwright.config.ts': `
|
||||
export default {
|
||||
use: { video: 'on' },
|
||||
};
|
||||
`,
|
||||
'reuse.test.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
let lastContextGuid;
|
||||
|
||||
test('one', async ({ context, page }) => {
|
||||
lastContextGuid = context._guid;
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
|
||||
test('two', async ({ context, page }) => {
|
||||
expect(context._guid).toBe(lastContextGuid);
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
`,
|
||||
}, { workers: 1 }, { PW_TEST_REUSE_CONTEXT: '1' });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(2);
|
||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'reuse-one', 'video.webm'))).toBeFalsy();
|
||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'reuse-two', 'video.webm'))).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should reuse context with trace if mode=when-possible', async ({ runInlineTest }, testInfo) => {
|
||||
const result = await runInlineTest({
|
||||
'playwright.config.ts': `
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ const test = baseTest.extend<{
|
|||
await use(async (reportFolder?: string) => {
|
||||
reportFolder ??= test.info().outputPath('playwright-report');
|
||||
server = startHtmlReportServer(reportFolder) as HttpServer;
|
||||
const location = await server.start();
|
||||
await page.goto(location);
|
||||
await server.start();
|
||||
await page.goto(server.urlPrefix('precise'));
|
||||
});
|
||||
await server?.stop();
|
||||
}
|
||||
|
|
@ -209,7 +209,7 @@ test('should merge into html with dependencies', async ({ runInlineTest, mergeRe
|
|||
const reportFiles = await fs.promises.readdir(reportDir);
|
||||
reportFiles.sort();
|
||||
expect(reportFiles).toEqual([expect.stringMatching(/report-.*.zip/), expect.stringMatching(/report-.*.zip/), expect.stringMatching(/report-.*.zip/)]);
|
||||
const { exitCode, output } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
const { exitCode, output } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
expect(output).not.toContain('To open last HTML report run:');
|
||||
|
|
@ -280,7 +280,7 @@ test('should merge blob into blob', async ({ runInlineTest, mergeReports, showRe
|
|||
}
|
||||
{
|
||||
const compinedBlobReportDir = test.info().outputPath('blob-report');
|
||||
const { exitCode } = await mergeReports(compinedBlobReportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html,json'] });
|
||||
const { exitCode } = await mergeReports(compinedBlobReportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html,json'] });
|
||||
expect(exitCode).toBe(0);
|
||||
expect(fs.existsSync(test.info().outputPath('report.json'))).toBe(true);
|
||||
await showReport();
|
||||
|
|
@ -335,7 +335,7 @@ test('be able to merge incomplete shards', async ({ runInlineTest, mergeReports,
|
|||
const reportFiles = await fs.promises.readdir(reportDir);
|
||||
reportFiles.sort();
|
||||
expect(reportFiles).toEqual([expect.stringMatching(/report-.*.zip/), expect.stringMatching(/report-.*.zip/)]);
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
await showReport();
|
||||
|
|
@ -374,7 +374,7 @@ test('total time is from test run not from merge', async ({ runInlineTest, merge
|
|||
await runInlineTest(files, { shard: `1/2` });
|
||||
await runInlineTest(files, { shard: `2/2` }, { PWTEST_BLOB_DO_NOT_REMOVE: '1' });
|
||||
|
||||
const { exitCode, output } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
const { exitCode, output } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
expect(output).not.toContain('To open last HTML report run:');
|
||||
|
|
@ -441,7 +441,7 @@ test('merge into list report by default', async ({ runInlineTest, mergeReports }
|
|||
const reportFiles = await fs.promises.readdir(reportDir);
|
||||
reportFiles.sort();
|
||||
expect(reportFiles).toEqual(['report-1.zip', 'report-2.zip', 'report-3.zip']);
|
||||
const { exitCode, output } = await mergeReports(reportDir, { PW_TEST_DEBUG_REPORTERS: '1', PW_TEST_DEBUG_REPORTERS_PRINT_STEPS: '1', PWTEST_TTY_WIDTH: '80' }, { additionalArgs: ['--reporter', 'list'] });
|
||||
const { exitCode, output } = await mergeReports(reportDir, { PW_TEST_DEBUG_REPORTERS: '1', PLAYWRIGHT_LIST_PRINT_STEPS: '1', PLAYWRIGHT_FORCE_TTY: '80' }, { additionalArgs: ['--reporter', 'list'] });
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
const text = stripAnsi(output);
|
||||
|
|
@ -520,7 +520,7 @@ test('should print progress', async ({ runInlineTest, mergeReports }) => {
|
|||
const reportFiles = await fs.promises.readdir(reportDir);
|
||||
reportFiles.sort();
|
||||
expect(reportFiles).toEqual(['report-1.zip', 'report-2.zip']);
|
||||
const { exitCode, output } = await mergeReports(reportDir, { PW_TEST_HTML_REPORT_OPEN: 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
const { exitCode, output } = await mergeReports(reportDir, { PLAYWRIGHT_HTML_OPEN: 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
const lines = output.split('\n');
|
||||
|
|
@ -571,7 +571,7 @@ test('preserve attachments', async ({ runInlineTest, mergeReports, showReport, p
|
|||
const reportFiles = await fs.promises.readdir(reportDir);
|
||||
reportFiles.sort();
|
||||
expect(reportFiles).toEqual(['report-1.zip']);
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
await showReport();
|
||||
|
|
@ -634,7 +634,7 @@ test('generate html with attachment urls', async ({ runInlineTest, mergeReports,
|
|||
const reportFiles = await fs.promises.readdir(reportDir);
|
||||
reportFiles.sort();
|
||||
expect(reportFiles).toEqual(['report-1.zip']);
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
const htmlReportDir = test.info().outputPath('playwright-report');
|
||||
|
|
@ -709,7 +709,7 @@ test('resource names should not clash between runs', async ({ runInlineTest, sho
|
|||
reportFiles.sort();
|
||||
expect(reportFiles).toEqual(['report-1.zip', 'report-2.zip']);
|
||||
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
await showReport();
|
||||
|
|
@ -781,7 +781,7 @@ test('multiple output reports', async ({ runInlineTest, mergeReports, showReport
|
|||
const reportFiles = await fs.promises.readdir(reportDir);
|
||||
reportFiles.sort();
|
||||
expect(reportFiles).toEqual(['report-1.zip']);
|
||||
const { exitCode, output } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html,line'] });
|
||||
const { exitCode, output } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html,line'] });
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
// Check that line reporter was called.
|
||||
|
|
@ -907,7 +907,7 @@ test('onError in the report', async ({ runInlineTest, mergeReports, showReport,
|
|||
const result = await runInlineTest(files, { shard: `1/3` }, { PWTEST_BOT_NAME: 'macos-node16-ttest' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
await showReport();
|
||||
|
|
@ -1149,7 +1149,7 @@ test('preserve steps in html report', async ({ runInlineTest, mergeReports, show
|
|||
// relative to the current directory.
|
||||
const mergeCwd = test.info().outputPath('foo');
|
||||
await fs.promises.mkdir(mergeCwd, { recursive: true });
|
||||
const { exitCode, output } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'], cwd: mergeCwd });
|
||||
const { exitCode, output } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'], cwd: mergeCwd });
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
expect(output).not.toContain('To open last HTML report run:');
|
||||
|
|
@ -1326,7 +1326,7 @@ test('keep projects with same name different bot name separate', async ({ runInl
|
|||
await runInlineTest(files('second'), undefined, { PWTEST_BOT_NAME: 'second', PWTEST_BLOB_DO_NOT_REMOVE: '1' });
|
||||
|
||||
const reportDir = test.info().outputPath('blob-report');
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
expect(exitCode).toBe(0);
|
||||
await showReport();
|
||||
await expect(page.locator('.subnav-item:has-text("Passed") .counter')).toHaveText('1');
|
||||
|
|
@ -1452,7 +1452,7 @@ test('merge-reports should throw if report version is from the future', async ({
|
|||
await fs.promises.rm(reportZipFile, { force: true });
|
||||
await zipReport(events, reportZipFile);
|
||||
|
||||
const { exitCode, output } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
const { exitCode, output } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
expect(exitCode).toBe(1);
|
||||
expect(output).toContain(`Error: Blob report report-2.zip was created with a newer version of Playwright.`);
|
||||
|
||||
|
|
@ -1496,7 +1496,7 @@ test('should merge blob reports with same name', async ({ runInlineTest, mergeRe
|
|||
await fs.promises.cp(reportZip, path.join(allReportsDir, 'report-1.zip'));
|
||||
await fs.promises.cp(reportZip, path.join(allReportsDir, 'report-2.zip'));
|
||||
|
||||
const { exitCode } = await mergeReports(allReportsDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
const { exitCode } = await mergeReports(allReportsDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
expect(exitCode).toBe(0);
|
||||
await showReport();
|
||||
await expect(page.locator('.subnav-item:has-text("All") .counter')).toHaveText('10');
|
||||
|
|
@ -1887,7 +1887,7 @@ test('preserve static annotations when tests did not run', async ({ runInlineTes
|
|||
`
|
||||
};
|
||||
await runInlineTest(files);
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
const { exitCode } = await mergeReports(reportDir, { 'PLAYWRIGHT_HTML_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] });
|
||||
expect(exitCode).toBe(0);
|
||||
await showReport();
|
||||
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ const test = baseTest.extend<{ showReport: (reportFolder?: string) => Promise<vo
|
|||
await use(async (reportFolder?: string) => {
|
||||
reportFolder ??= testInfo.outputPath('playwright-report');
|
||||
server = startHtmlReportServer(reportFolder) as HttpServer;
|
||||
const location = await server.start();
|
||||
await page.goto(location);
|
||||
await server.start();
|
||||
await page.goto(server.urlPrefix('precise'));
|
||||
});
|
||||
await server?.stop();
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(testInfo.retry).toBe(1);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html', retries: 1 }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html', retries: 1 }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
await showReport();
|
||||
|
||||
|
|
@ -95,6 +95,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
// Note: using PW_TEST_HTML_REPORT_OPEN to test backwards compatibility.
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
|
@ -113,7 +114,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await expect(page.locator('.attachment-body')).toHaveText(/TESTID=.*/);
|
||||
});
|
||||
|
||||
test('should not throw when PW_TEST_HTML_REPORT_OPEN value is invalid', async ({ runInlineTest, page, showReport }, testInfo) => {
|
||||
test('should not throw when PLAYWRIGHT_HTML_OPEN value is invalid', async ({ runInlineTest, page, showReport }, testInfo) => {
|
||||
const invalidOption = 'invalid-option';
|
||||
const result = await runInlineTest({
|
||||
'playwright.config.ts': `
|
||||
|
|
@ -125,7 +126,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(2).toEqual(2);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: invalidOption });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: invalidOption });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
});
|
||||
|
|
@ -143,7 +144,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
testInfo.attachments.push({ name: 'screenshot', path: screenshot, contentType: 'image/png' });
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
||||
|
|
@ -169,7 +170,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await expect(screenshot).toMatchSnapshot('expected.png');
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
|
||||
|
|
@ -243,7 +244,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await expect.soft(page).toHaveScreenshot({ timeout: 1000 });
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
|
||||
|
|
@ -278,7 +279,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await expect.soft(screenshot).toMatchSnapshot('expected.png');
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
|
||||
|
|
@ -309,7 +310,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await expect.soft(page).toHaveScreenshot({ timeout: 1000 });
|
||||
});
|
||||
`,
|
||||
}, { 'reporter': 'dot,html', 'update-snapshots': true }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { 'reporter': 'dot,html', 'update-snapshots': true }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
|
||||
|
|
@ -344,7 +345,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await expect(screenshot).toMatchSnapshot('expected');
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
|
||||
|
|
@ -373,7 +374,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await expect(true).toBeFalsy();
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
|
||||
|
|
@ -404,7 +405,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await page.evaluate('2 + 2');
|
||||
});
|
||||
`
|
||||
}, {}, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, {}, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
||||
|
|
@ -432,7 +433,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await expect(true).toBeFalsy();
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
|
||||
|
|
@ -452,7 +453,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await expect(true).toBeFalsy();
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
|
||||
|
|
@ -475,7 +476,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await evaluateWrapper(page, '2 + 2');
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
||||
|
|
@ -508,7 +509,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await page.evaluate('2 + 2');
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
||||
|
|
@ -538,7 +539,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await page.evaluate('2 + 2');
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
||||
|
|
@ -562,7 +563,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await request.dispose();
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
||||
|
|
@ -600,7 +601,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
]);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
||||
|
|
@ -625,7 +626,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await page.evaluate('2 + 2');
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
||||
|
|
@ -683,7 +684,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
});
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(0);
|
||||
|
||||
|
|
@ -726,7 +727,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
test.info().annotations.push({ type: 'issue', description: 'I am not interested in this test' });
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
||||
|
|
@ -746,7 +747,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
test.info().annotations.push({ type: 'issue', description: '${server.EMPTY_PAGE}' });
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
||||
|
|
@ -789,7 +790,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
});
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
|
||||
await showReport();
|
||||
|
|
@ -814,7 +815,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await testInfo.attach('example.ext with spaces', { body: Buffer.from('b'), contentType: 'madeup' });
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
await showReport();
|
||||
await page.getByRole('link', { name: 'passing' }).click();
|
||||
|
|
@ -867,7 +868,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect('new').toMatchSnapshot('snapshot.txt');
|
||||
});
|
||||
`
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
await showReport();
|
||||
await page.click('text="is a test"');
|
||||
|
|
@ -894,7 +895,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect('newcommon').toMatchSnapshot('snapshot.txt');
|
||||
});
|
||||
`
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
await showReport();
|
||||
await page.click('text="is a test"');
|
||||
|
|
@ -912,7 +913,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
throw new Error('ouch');
|
||||
});
|
||||
`
|
||||
}, { 'reporter': 'dot,html', 'repeat-each': 3 }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { 'reporter': 'dot,html', 'repeat-each': 3 }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
await showReport();
|
||||
|
||||
|
|
@ -937,7 +938,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(2).toEqual(2);
|
||||
});
|
||||
`
|
||||
}, { 'reporter': 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { 'reporter': 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
await showReport();
|
||||
|
||||
|
|
@ -956,7 +957,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
test('sample', async ({}) => { expect(2).toBe(2); });
|
||||
`,
|
||||
'a.spec.js': `require('./inner')`
|
||||
}, { 'reporter': 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { 'reporter': 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
await showReport();
|
||||
await expect(page.locator('text=a.spec.js')).toBeVisible();
|
||||
|
|
@ -997,7 +998,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
await execGit(['commit', '-m', 'awesome commit message']);
|
||||
|
||||
const result = await runInlineTest(files, { reporter: 'dot,html' }, {
|
||||
PW_TEST_HTML_REPORT_OPEN: 'never',
|
||||
PLAYWRIGHT_HTML_OPEN: 'never',
|
||||
GITHUB_REPOSITORY: 'microsoft/playwright-example-for-test',
|
||||
GITHUB_RUN_ID: 'example-run-id',
|
||||
GITHUB_SERVER_URL: 'https://playwright.dev',
|
||||
|
|
@ -1043,7 +1044,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
import { test, expect } from '@playwright/test';
|
||||
test('sample', async ({}) => { expect(2).toBe(2); });
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never', GITHUB_REPOSITORY: 'microsoft/playwright-example-for-test', GITHUB_RUN_ID: 'example-run-id', GITHUB_SERVER_URL: 'https://playwright.dev', GITHUB_SHA: 'example-sha' }, undefined);
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never', GITHUB_REPOSITORY: 'microsoft/playwright-example-for-test', GITHUB_RUN_ID: 'example-run-id', GITHUB_SERVER_URL: 'https://playwright.dev', GITHUB_SHA: 'example-sha' }, undefined);
|
||||
|
||||
await showReport();
|
||||
|
||||
|
|
@ -1071,7 +1072,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
import { test, expect } from '@playwright/test';
|
||||
test('my sample test', async ({}) => { expect(2).toBe(2); });
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' }, undefined);
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' }, undefined);
|
||||
|
||||
await showReport();
|
||||
|
||||
|
|
@ -1095,7 +1096,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
import { test, expect } from '@playwright/test';
|
||||
test('my sample test', async ({}) => { expect(2).toBe(2); });
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
await showReport();
|
||||
|
||||
|
|
@ -1177,7 +1178,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
test('pass', ({}, testInfo) => {
|
||||
});
|
||||
`
|
||||
}, { 'reporter': 'html,line' }, { PW_TEST_HTML_REPORT_OPEN: 'never' }, {
|
||||
}, { 'reporter': 'html,line' }, { PLAYWRIGHT_HTML_OPEN: 'never' }, {
|
||||
cwd: 'foo/bar/baz/tests',
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
|
|
@ -1201,7 +1202,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
test('pass', ({}, testInfo) => {
|
||||
});
|
||||
`
|
||||
}, { 'reporter': 'html,line' }, { 'PW_TEST_HTML_REPORT_OPEN': 'never', 'PLAYWRIGHT_HTML_REPORT': '../my-report' }, {
|
||||
}, { 'reporter': 'html,line' }, { 'PLAYWRIGHT_HTML_OPEN': 'never', 'PLAYWRIGHT_HTML_OUTPUT_DIR': '../my-report' }, {
|
||||
cwd: 'foo/bar/baz/tests',
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
|
|
@ -1250,7 +1251,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(2);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(3);
|
||||
|
|
@ -1324,7 +1325,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(3);
|
||||
|
|
@ -1366,7 +1367,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(3);
|
||||
|
|
@ -1407,7 +1408,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(3);
|
||||
|
|
@ -1445,7 +1446,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
});
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(2);
|
||||
|
|
@ -1523,7 +1524,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
});
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(5);
|
||||
|
|
@ -1566,7 +1567,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(2);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(1);
|
||||
|
|
@ -1615,7 +1616,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
});
|
||||
}
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(7);
|
||||
|
|
@ -1689,7 +1690,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(2);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(2);
|
||||
|
|
@ -1755,7 +1756,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(2);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(2);
|
||||
|
|
@ -1804,7 +1805,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(2);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(1);
|
||||
|
|
@ -1882,7 +1883,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(3);
|
||||
|
|
@ -2021,7 +2022,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(0);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(3);
|
||||
|
|
@ -2110,7 +2111,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
test('passes', () => {});
|
||||
}
|
||||
`,
|
||||
}, { reporter: 'html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
await showReport();
|
||||
|
||||
|
|
@ -2139,7 +2140,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
});
|
||||
test('test 6', async ({}) => {});
|
||||
`,
|
||||
}, { reporter: 'html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
await showReport();
|
||||
|
||||
|
|
@ -2167,7 +2168,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
test('b test 1', async ({}) => {});
|
||||
test('b test 2', async ({}) => {});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(4);
|
||||
|
|
@ -2197,7 +2198,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
test('failed title', async ({}) => { expect(1).toBe(1); });
|
||||
test('passes title', async ({}) => { expect(1).toBe(2); });
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(1);
|
||||
|
|
@ -2220,7 +2221,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
test('test1', async ({}) => { expect(1).toBe(1); });
|
||||
test('test2', async ({}) => { expect(1).toBe(2); });
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(1);
|
||||
|
|
@ -2257,7 +2258,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
|
@ -2286,7 +2287,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
|
@ -2315,7 +2316,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
|
@ -2344,7 +2345,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
expect(1).toBe(1);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
|
@ -2374,7 +2375,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
'playwright.config.ts': `
|
||||
export default { globalTeardown: './globalTeardown.ts' };
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(1);
|
||||
|
|
@ -2395,7 +2396,7 @@ for (const useIntermediateMergeReport of [false] as const) {
|
|||
});
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
|
|
|||
|
|
@ -281,7 +281,7 @@ test.describe('report location', () => {
|
|||
test('pass', ({}, testInfo) => {
|
||||
});
|
||||
`
|
||||
}, { 'reporter': 'json' }, { 'PW_TEST_HTML_REPORT_OPEN': 'never', 'PLAYWRIGHT_JSON_OUTPUT_NAME': '../my-report.json' }, {
|
||||
}, { 'reporter': 'json' }, { 'PLAYWRIGHT_JSON_OUTPUT_NAME': '../my-report.json' }, {
|
||||
cwd: 'foo/bar/baz/tests',
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
|
|
@ -302,7 +302,7 @@ test.describe('report location', () => {
|
|||
test('pass', ({}, testInfo) => {
|
||||
});
|
||||
`
|
||||
}, { 'reporter': 'json' }, { 'PW_TEST_HTML_REPORT_OPEN': 'never', 'PLAYWRIGHT_JSON_OUTPUT_FILE': '../my-report.json' }, {
|
||||
}, { 'reporter': 'json' }, { 'PLAYWRIGHT_JSON_OUTPUT_FILE': '../my-report.json' }, {
|
||||
cwd: 'foo/bar/baz/tests',
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { test, expect } from './playwright-test-fixtures';
|
||||
import { test, expect, stripAnsi } from './playwright-test-fixtures';
|
||||
|
||||
const DOES_NOT_SUPPORT_UTF8_IN_TERMINAL = process.platform === 'win32' && process.env.TERM_PROGRAM !== 'vscode' && !process.env.WT_SESSION;
|
||||
const POSITIVE_STATUS_MARK = DOES_NOT_SUPPORT_UTF8_IN_TERMINAL ? 'ok' : '✓ ';
|
||||
|
|
@ -70,7 +70,7 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
|||
});
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'list' }, { PW_TEST_DEBUG_REPORTERS: '1', PW_TEST_DEBUG_REPORTERS_PRINT_STEPS: '1', PWTEST_TTY_WIDTH: '80' });
|
||||
}, { reporter: 'list' }, { PW_TEST_DEBUG_REPORTERS: '1', PLAYWRIGHT_LIST_PRINT_STEPS: '1', PLAYWRIGHT_FORCE_TTY: '80' });
|
||||
const text = result.output;
|
||||
const lines = text.split('\n').filter(l => l.match(/^\d :/)).map(l => l.replace(/[.\d]+m?s/, 'Xms'));
|
||||
lines.pop(); // Remove last item that contains [v] and time in ms.
|
||||
|
|
@ -105,7 +105,7 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
|||
await test.step('inner 2.2', async () => {});
|
||||
});
|
||||
});`,
|
||||
}, { reporter: 'list' }, { PW_TEST_DEBUG_REPORTERS: '1', PWTEST_TTY_WIDTH: '80' });
|
||||
}, { reporter: 'list' }, { PW_TEST_DEBUG_REPORTERS: '1', PLAYWRIGHT_FORCE_TTY: '80' });
|
||||
const text = result.output;
|
||||
const lines = text.split('\n').filter(l => l.match(/^\d :/)).map(l => l.replace(/[.\d]+m?s/, 'Xms'));
|
||||
lines.pop(); // Remove last item that contains [v] and time in ms.
|
||||
|
|
@ -135,7 +135,7 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
|||
console.log('a'.repeat(80) + 'b'.repeat(20));
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'list' }, { PWTEST_TTY_WIDTH: TTY_WIDTH + '' });
|
||||
}, { reporter: 'list' }, { PLAYWRIGHT_FORCE_TTY: TTY_WIDTH + '' });
|
||||
|
||||
const renderedText = simpleAnsiRenderer(result.rawOutput, TTY_WIDTH);
|
||||
if (process.platform === 'win32')
|
||||
|
|
@ -154,7 +154,7 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
|||
expect(testInfo.retry).toBe(1);
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'list', retries: '1' }, { PW_TEST_DEBUG_REPORTERS: '1', PWTEST_TTY_WIDTH: '80' });
|
||||
}, { reporter: 'list', retries: '1' }, { PW_TEST_DEBUG_REPORTERS: '1', PLAYWRIGHT_FORCE_TTY: '80' });
|
||||
const text = result.output;
|
||||
const lines = text.split('\n').filter(l => l.startsWith('0 :') || l.startsWith('1 :')).map(l => l.replace(/\d+(\.\d+)?m?s/, 'XXms'));
|
||||
|
||||
|
|
@ -185,10 +185,10 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
|||
test.skip('skipped very long name', async () => {
|
||||
});
|
||||
`,
|
||||
}, { reporter: 'list', retries: 0 }, { PWTEST_TTY_WIDTH: '50' });
|
||||
}, { reporter: 'list', retries: 0 }, { PLAYWRIGHT_FORCE_TTY: '50' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
|
||||
const lines = result.output.split('\n').slice(3, 11);
|
||||
const lines = result.rawOutput.split('\n').map(line => line.split('\x1B[22m\x1B[1E')).flat().map(line => stripAnsi(line)).filter(line => line.trim()).slice(1, 9);
|
||||
expect(lines.every(line => line.length <= 50)).toBe(true);
|
||||
|
||||
expect(lines[0]).toBe(` 1 …a.test.ts:3:15 › failure in very long name`);
|
||||
|
|
|
|||
|
|
@ -514,3 +514,47 @@ test('should report up to 3 timeout errors', async ({ runInlineTest }) => {
|
|||
expect(result.output).toContain('Test timeout of 1000ms exceeded while running "afterEach" hook.');
|
||||
expect(result.output).toContain('Worker teardown timeout of 1000ms exceeded while tearing down "autoWorker".');
|
||||
});
|
||||
|
||||
test('should complain when worker fixture times out during worker cleanup', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.spec.ts': `
|
||||
import { test as base, expect } from '@playwright/test';
|
||||
const test = base.extend({
|
||||
slowTeardown: [async ({}, use) => {
|
||||
await use('hey');
|
||||
await new Promise(f => setTimeout(f, 2000));
|
||||
}, { scope: 'worker', auto: true, timeout: 400 }],
|
||||
});
|
||||
test('test ok', async ({ slowTeardown }) => {
|
||||
expect(slowTeardown).toBe('hey');
|
||||
});
|
||||
`
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.output).toContain(`Fixture "slowTeardown" timeout of 400ms exceeded during teardown.`);
|
||||
});
|
||||
|
||||
test('should allow custom worker fixture timeout longer than force exit cap', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.spec.ts': `
|
||||
import { test as base, expect } from '@playwright/test';
|
||||
const test = base.extend({
|
||||
slowTeardown: [async ({}, use) => {
|
||||
await use('hey');
|
||||
await new Promise(f => setTimeout(f, 1500));
|
||||
console.log('output from teardown');
|
||||
throw new Error('Oh my!');
|
||||
}, { scope: 'worker', auto: true, timeout: 2000 }],
|
||||
});
|
||||
test('test ok', async ({ slowTeardown }) => {
|
||||
expect(slowTeardown).toBe('hey');
|
||||
});
|
||||
`
|
||||
}, {}, { PWTEST_FORCE_EXIT_TIMEOUT: '400' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.output).toContain(`output from teardown`);
|
||||
expect(result.output).toContain(`Error: Oh my!`);
|
||||
expect(result.output).toContain(`1 error was not a part of any test, see above for details`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -285,3 +285,24 @@ test('should reveal errors in the sourcetab', async ({ runUITest }) => {
|
|||
await page.getByText('a.spec.ts:4', { exact: true }).click();
|
||||
await expect(page.locator('.source-line-running')).toContainText(`throw new Error('Oh my');`);
|
||||
});
|
||||
|
||||
test('should show request source context id', async ({ runUITest, server }) => {
|
||||
const { page } = await runUITest({
|
||||
'a.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('pass', async ({ page, context, request }) => {
|
||||
await page.goto('${server.EMPTY_PAGE}');
|
||||
const page2 = await context.newPage();
|
||||
await page2.goto('${server.EMPTY_PAGE}');
|
||||
await request.get('${server.EMPTY_PAGE}');
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await page.getByText('pass').dblclick();
|
||||
await page.getByText('Network', { exact: true }).click();
|
||||
await expect(page.locator('span').filter({ hasText: 'Source' })).toBeVisible();
|
||||
await expect(page.getByText('page#1')).toBeVisible();
|
||||
await expect(page.getByText('page#2')).toBeVisible();
|
||||
await expect(page.getByText('api#1')).toBeVisible();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -77,7 +77,10 @@ class ApiParser {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
const clazz = new docs.Class(extractMetainfo(node), name, [], extendsName, extractComments(node));
|
||||
const metainfo = extractMetainfo(node);
|
||||
const clazz = new docs.Class(metainfo, name, [], extendsName, extractComments(node));
|
||||
if (metainfo.hidden)
|
||||
return;
|
||||
this.classes.set(clazz.name, clazz);
|
||||
}
|
||||
|
||||
|
|
@ -103,13 +106,14 @@ class ApiParser {
|
|||
returnType = new docs.Type('void');
|
||||
|
||||
const comments = extractComments(spec);
|
||||
const metainfo = extractMetainfo(spec);
|
||||
let member;
|
||||
if (match[1] === 'event')
|
||||
member = docs.Member.createEvent(extractMetainfo(spec), name, returnType, comments);
|
||||
member = docs.Member.createEvent(metainfo, name, returnType, comments);
|
||||
if (match[1] === 'property')
|
||||
member = docs.Member.createProperty(extractMetainfo(spec), name, returnType, comments, !optional);
|
||||
member = docs.Member.createProperty(metainfo, name, returnType, comments, !optional);
|
||||
if (['method', 'async method', 'optional method', 'optional async method'].includes(match[1])) {
|
||||
member = docs.Member.createMethod(extractMetainfo(spec), name, [], returnType, comments);
|
||||
member = docs.Member.createMethod(metainfo, name, [], returnType, comments);
|
||||
if (match[1].includes('async'))
|
||||
member.async = true;
|
||||
if (match[1].includes('optional'))
|
||||
|
|
@ -119,6 +123,11 @@ class ApiParser {
|
|||
throw new Error('Unknown member: ' + spec.text);
|
||||
|
||||
const clazz = /** @type {docs.Class} */(this.classes.get(match[2]));
|
||||
if (!clazz)
|
||||
throw new Error(`Unknown class ${match[2]} for member: ` + spec.text);
|
||||
if (metainfo.hidden)
|
||||
return;
|
||||
|
||||
const existingMember = clazz.membersArray.find(m => m.name === name && m.kind === member.kind);
|
||||
if (existingMember && isTypeOverride(existingMember, member)) {
|
||||
for (const lang of member?.langs?.only || []) {
|
||||
|
|
@ -157,6 +166,8 @@ class ApiParser {
|
|||
throw new Error('Invalid member name ' + spec.text);
|
||||
if (match[1] === 'param') {
|
||||
const arg = this.parseProperty(spec);
|
||||
if (!arg)
|
||||
return;
|
||||
arg.name = name;
|
||||
const existingArg = method.argsArray.find(m => m.name === arg.name);
|
||||
if (existingArg && isTypeOverride(existingArg, arg)) {
|
||||
|
|
@ -171,13 +182,15 @@ class ApiParser {
|
|||
}
|
||||
} else {
|
||||
// match[1] === 'option'
|
||||
const p = this.parseProperty(spec);
|
||||
if (!p)
|
||||
return;
|
||||
let options = method.argsArray.find(o => o.name === 'options');
|
||||
if (!options) {
|
||||
const type = new docs.Type('Object', []);
|
||||
options = docs.Member.createProperty({ langs: {}, experimental: false, since: 'v1.0', deprecated: undefined, discouraged: undefined }, 'options', type, undefined, false);
|
||||
options = docs.Member.createProperty({ langs: {}, since: 'v1.0', deprecated: undefined, discouraged: undefined }, 'options', type, undefined, false);
|
||||
method.argsArray.push(options);
|
||||
}
|
||||
const p = this.parseProperty(spec);
|
||||
p.required = false;
|
||||
// @ts-ignore
|
||||
options.type.properties.push(p);
|
||||
|
|
@ -186,6 +199,7 @@ class ApiParser {
|
|||
|
||||
/**
|
||||
* @param {MarkdownHeaderNode} spec
|
||||
* @returns {docs.Member | null}
|
||||
*/
|
||||
parseProperty(spec) {
|
||||
const param = childrenWithoutProperties(spec)[0];
|
||||
|
|
@ -196,12 +210,15 @@ class ApiParser {
|
|||
const name = text.substring(0, typeStart).replace(/\`/g, '').trim();
|
||||
const comments = extractComments(spec);
|
||||
const { type, optional } = this.parseType(/** @type {MarkdownLiNode} */(param));
|
||||
return docs.Member.createProperty(extractMetainfo(spec), name, type, comments, !optional);
|
||||
const metainfo = extractMetainfo(spec);
|
||||
if (metainfo.hidden)
|
||||
return null;
|
||||
return docs.Member.createProperty(metainfo, name, type, comments, !optional);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MarkdownLiNode} spec
|
||||
* @return {{ type: docs.Type, optional: boolean, experimental: boolean }}
|
||||
* @return {{ type: docs.Type, optional: boolean }}
|
||||
*/
|
||||
parseType(spec) {
|
||||
const arg = parseVariable(spec.text);
|
||||
|
|
@ -210,16 +227,16 @@ class ApiParser {
|
|||
const { name, text } = parseVariable(/** @type {string} */(child.text));
|
||||
const comments = /** @type {MarkdownNode[]} */ ([{ type: 'text', text }]);
|
||||
const childType = this.parseType(child);
|
||||
properties.push(docs.Member.createProperty({ langs: {}, experimental: childType.experimental, since: 'v1.0', deprecated: undefined, discouraged: undefined }, name, childType.type, comments, !childType.optional));
|
||||
properties.push(docs.Member.createProperty({ langs: {}, since: 'v1.0', deprecated: undefined, discouraged: undefined }, name, childType.type, comments, !childType.optional));
|
||||
}
|
||||
const type = docs.Type.parse(arg.type, properties);
|
||||
return { type, optional: arg.optional, experimental: arg.experimental };
|
||||
return { type, optional: arg.optional };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} line
|
||||
* @returns {{ name: string, type: string, text: string, optional: boolean, experimental: boolean }}
|
||||
* @returns {{ name: string, type: string, text: string, optional: boolean }}
|
||||
*/
|
||||
function parseVariable(line) {
|
||||
let match = line.match(/^`([^`]+)` (.*)/);
|
||||
|
|
@ -234,12 +251,9 @@ function parseVariable(line) {
|
|||
const name = match[1];
|
||||
let remainder = match[2];
|
||||
let optional = false;
|
||||
let experimental = false;
|
||||
while ('?e'.includes(remainder[0])) {
|
||||
while ('?'.includes(remainder[0])) {
|
||||
if (remainder[0] === '?')
|
||||
optional = true;
|
||||
else if (remainder[0] === 'e')
|
||||
experimental = true;
|
||||
remainder = remainder.substring(1);
|
||||
}
|
||||
if (!remainder.startsWith('<'))
|
||||
|
|
@ -252,7 +266,7 @@ function parseVariable(line) {
|
|||
if (c === '>')
|
||||
--depth;
|
||||
if (depth === 0)
|
||||
return { name, type: remainder.substring(1, i), text: remainder.substring(i + 2), optional, experimental };
|
||||
return { name, type: remainder.substring(1, i), text: remainder.substring(i + 2), optional };
|
||||
}
|
||||
throw new Error('Should not be reached, line: ' + line);
|
||||
}
|
||||
|
|
@ -344,15 +358,15 @@ function parseApi(apiDir, paramsPath) {
|
|||
|
||||
/**
|
||||
* @param {MarkdownHeaderNode} spec
|
||||
* @returns {import('./documentation').Metainfo}
|
||||
* @returns {import('./documentation').Metainfo & { hidden: boolean }}
|
||||
*/
|
||||
function extractMetainfo(spec) {
|
||||
return {
|
||||
langs: extractLangs(spec),
|
||||
since: extractSince(spec),
|
||||
experimental: extractExperimental(spec),
|
||||
deprecated: extractAttribute(spec, 'deprecated'),
|
||||
discouraged: extractAttribute(spec, 'discouraged'),
|
||||
hidden: extractHidden(spec),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -402,9 +416,9 @@ function extractSince(spec) {
|
|||
* @param {MarkdownHeaderNode} spec
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function extractExperimental(spec) {
|
||||
function extractHidden(spec) {
|
||||
for (const child of spec.children) {
|
||||
if (child.type === 'li' && child.liType === 'bullet' && child.text === 'experimental')
|
||||
if (child.type === 'li' && child.liType === 'bullet' && child.text === 'hidden')
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -429,7 +443,7 @@ function extractSince(spec) {
|
|||
*/
|
||||
function childrenWithoutProperties(spec) {
|
||||
return (spec.children || []).filter(c => {
|
||||
const isProperty = c.type === 'li' && c.liType === 'bullet' && (c.text.startsWith('langs:') || c.text.startsWith('since:') || c.text.startsWith('deprecated:') || c.text.startsWith('discouraged:') || c.text === 'experimental');
|
||||
const isProperty = c.type === 'li' && c.liType === 'bullet' && (c.text.startsWith('langs:') || c.text.startsWith('since:') || c.text.startsWith('deprecated:') || c.text.startsWith('discouraged:') || c.text === 'hidden');
|
||||
return !isProperty;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ const md = require('../markdown');
|
|||
* since: string,
|
||||
* deprecated?: string | undefined,
|
||||
* discouraged?: string | undefined,
|
||||
* experimental: boolean
|
||||
* }} Metainfo
|
||||
*/
|
||||
|
||||
|
|
@ -132,18 +131,6 @@ class Documentation {
|
|||
this.index();
|
||||
}
|
||||
|
||||
filterOutExperimental() {
|
||||
const classesArray = [];
|
||||
for (const clazz of this.classesArray) {
|
||||
if (clazz.experimental)
|
||||
continue;
|
||||
clazz.filterOutExperimental();
|
||||
classesArray.push(clazz);
|
||||
}
|
||||
this.classesArray = classesArray;
|
||||
this.index();
|
||||
}
|
||||
|
||||
index() {
|
||||
for (const cls of this.classesArray) {
|
||||
this.classes.set(cls.name, cls);
|
||||
|
|
@ -231,7 +218,6 @@ class Documentation {
|
|||
*/
|
||||
constructor(metainfo, name, membersArray, extendsName = null, spec = undefined) {
|
||||
this.langs = metainfo.langs;
|
||||
this.experimental = metainfo.experimental;
|
||||
this.since = metainfo.since;
|
||||
this.deprecated = metainfo.deprecated;
|
||||
this.discouraged = metainfo.discouraged;
|
||||
|
|
@ -286,7 +272,7 @@ class Documentation {
|
|||
}
|
||||
|
||||
clone() {
|
||||
const cls = new Class({ langs: this.langs, experimental: this.experimental, since: this.since, deprecated: this.deprecated, discouraged: this.discouraged }, this.name, this.membersArray.map(m => m.clone()), this.extends, this.spec);
|
||||
const cls = new Class({ langs: this.langs, since: this.since, deprecated: this.deprecated, discouraged: this.discouraged }, this.name, this.membersArray.map(m => m.clone()), this.extends, this.spec);
|
||||
cls.comment = this.comment;
|
||||
return cls;
|
||||
}
|
||||
|
|
@ -306,17 +292,6 @@ class Documentation {
|
|||
this.membersArray = membersArray;
|
||||
}
|
||||
|
||||
filterOutExperimental() {
|
||||
const membersArray = [];
|
||||
for (const member of this.membersArray) {
|
||||
if (member.experimental)
|
||||
continue;
|
||||
member.filterOutExperimental();
|
||||
membersArray.push(member);
|
||||
}
|
||||
this.membersArray = membersArray;
|
||||
}
|
||||
|
||||
sortMembers() {
|
||||
/**
|
||||
* @param {Member} member
|
||||
|
|
@ -362,7 +337,6 @@ class Member {
|
|||
constructor(kind, metainfo, name, type, argsArray, spec = undefined, required = true) {
|
||||
this.kind = kind;
|
||||
this.langs = metainfo.langs;
|
||||
this.experimental = metainfo.experimental;
|
||||
this.since = metainfo.since;
|
||||
this.deprecated = metainfo.deprecated;
|
||||
this.discouraged = metainfo.discouraged;
|
||||
|
|
@ -447,22 +421,8 @@ class Member {
|
|||
}
|
||||
}
|
||||
|
||||
filterOutExperimental() {
|
||||
if (!this.type)
|
||||
return;
|
||||
this.type.filterOutExperimental();
|
||||
const argsArray = [];
|
||||
for (const arg of this.argsArray) {
|
||||
if (arg.experimental || !arg.type)
|
||||
continue;
|
||||
arg.type.filterOutExperimental();
|
||||
argsArray.push(arg);
|
||||
}
|
||||
this.argsArray = argsArray;
|
||||
}
|
||||
|
||||
clone() {
|
||||
const result = new Member(this.kind, { langs: this.langs, experimental: this.experimental, since: this.since, deprecated: this.deprecated, discouraged: this.discouraged }, this.name, this.type?.clone(), this.argsArray.map(arg => arg.clone()), this.spec, this.required);
|
||||
const result = new Member(this.kind, { langs: this.langs, since: this.since, deprecated: this.deprecated, discouraged: this.discouraged }, this.name, this.type?.clone(), this.argsArray.map(arg => arg.clone()), this.spec, this.required);
|
||||
result.alias = this.alias;
|
||||
result.async = this.async;
|
||||
result.paramOrOption = this.paramOrOption;
|
||||
|
|
@ -671,19 +631,6 @@ class Type {
|
|||
this.properties = properties;
|
||||
}
|
||||
|
||||
filterOutExperimental() {
|
||||
if (!this.properties)
|
||||
return;
|
||||
const properties = [];
|
||||
for (const prop of this.properties) {
|
||||
if (prop.experimental)
|
||||
continue;
|
||||
prop.filterOutExperimental();
|
||||
properties.push(prop);
|
||||
}
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Type[]} result
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ class TypesGenerator {
|
|||
* ignoreMissing?: Set<string>,
|
||||
* doNotExportClassNames?: Set<string>,
|
||||
* doNotGenerate?: Set<string>,
|
||||
* includeExperimental?: boolean,
|
||||
* }} options
|
||||
*/
|
||||
constructor(options) {
|
||||
|
|
@ -50,8 +49,6 @@ class TypesGenerator {
|
|||
this.doNotExportClassNames = options.doNotExportClassNames || new Set();
|
||||
this.doNotGenerate = options.doNotGenerate || new Set();
|
||||
this.documentation.filterForLanguage('js');
|
||||
if (!options.includeExperimental)
|
||||
this.documentation.filterOutExperimental();
|
||||
this.documentation.copyDocsFromSuperclasses([]);
|
||||
this.injectDisposeAsync();
|
||||
}
|
||||
|
|
@ -65,7 +62,7 @@ class TypesGenerator {
|
|||
continue;
|
||||
if (!member.async)
|
||||
continue;
|
||||
newMember = new docs.Member('method', { langs: {}, since: '1.0', experimental: false }, '[Symbol.asyncDispose]', null, []);
|
||||
newMember = new docs.Member('method', { langs: {}, since: '1.0' }, '[Symbol.asyncDispose]', null, []);
|
||||
newMember.async = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -98,12 +95,20 @@ class TypesGenerator {
|
|||
}, (className, methodName, overloadIndex) => {
|
||||
if (className === 'SuiteFunction' && methodName === '__call') {
|
||||
const cls = this.documentation.classes.get('Test');
|
||||
if (!cls)
|
||||
throw new Error(`Unknown class "Test"`);
|
||||
const method = cls.membersArray.find(m => m.alias === 'describe');
|
||||
if (!method)
|
||||
throw new Error(`Unknown method "Test.describe"`);
|
||||
return this.memberJSDOC(method, ' ').trimLeft();
|
||||
}
|
||||
if (className === 'TestFunction' && methodName === '__call') {
|
||||
const cls = this.documentation.classes.get('Test');
|
||||
if (!cls)
|
||||
throw new Error(`Unknown class "Test"`);
|
||||
const method = cls.membersArray.find(m => m.alias === '(call)');
|
||||
if (!method)
|
||||
throw new Error(`Unknown method "Test.(call)"`);
|
||||
return this.memberJSDOC(method, ' ').trimLeft();
|
||||
}
|
||||
|
||||
|
|
@ -137,6 +142,8 @@ class TypesGenerator {
|
|||
.filter(cls => !handledClasses.has(cls.name));
|
||||
{
|
||||
const playwright = this.documentation.classesArray.find(c => c.name === 'Playwright');
|
||||
if (!playwright)
|
||||
throw new Error(`Unknown class "Playwright"`);
|
||||
playwright.membersArray = playwright.membersArray.filter(member => !['errors', 'devices'].includes(member.name));
|
||||
playwright.index();
|
||||
}
|
||||
|
|
@ -327,19 +334,20 @@ class TypesGenerator {
|
|||
hasOwnMethod(classDesc, member) {
|
||||
if (this.handledMethods.has(`${classDesc.name}.${member.alias}#${member.overloadIndex}`))
|
||||
return false;
|
||||
while (classDesc = this.parentClass(classDesc)) {
|
||||
if (classDesc.members.has(member.alias))
|
||||
let parent = /** @type {docs.Class | undefined} */ (classDesc);
|
||||
while (parent = this.parentClass(parent)) {
|
||||
if (parent.members.has(member.alias))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {docs.Class} classDesc
|
||||
* @param {docs.Class | undefined} classDesc
|
||||
*/
|
||||
parentClass(classDesc) {
|
||||
if (!classDesc.extends)
|
||||
return null;
|
||||
if (!classDesc || !classDesc.extends)
|
||||
return;
|
||||
return this.documentation.classes.get(classDesc.extends);
|
||||
}
|
||||
|
||||
|
|
@ -427,6 +435,8 @@ class TypesGenerator {
|
|||
const name = namespace.map(n => n[0].toUpperCase() + n.substring(1)).join('');
|
||||
const shouldExport = exported[name];
|
||||
const properties = namespace[namespace.length - 1] === 'options' ? type.sortedProperties() : type.properties;
|
||||
if (!properties)
|
||||
throw new Error(`Object type must have properties`);
|
||||
if (!this.objectDefinitions.some(o => o.name === name))
|
||||
this.objectDefinitions.push({ name, properties });
|
||||
if (shouldExport) {
|
||||
|
|
@ -503,15 +513,13 @@ class TypesGenerator {
|
|||
]);
|
||||
|
||||
/**
|
||||
* @param {boolean} includeExperimental
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function generateCoreTypes(includeExperimental) {
|
||||
async function generateCoreTypes() {
|
||||
const documentation = coreDocumentation.clone();
|
||||
const generator = new TypesGenerator({
|
||||
documentation,
|
||||
doNotGenerate: assertionClasses,
|
||||
includeExperimental,
|
||||
});
|
||||
let types = await generator.generateTypes(path.join(__dirname, 'overrides.d.ts'));
|
||||
const namedDevices = Object.keys(devices).map(name => ` ${JSON.stringify(name)}: DeviceDescriptor;`).join('\n');
|
||||
|
|
@ -534,10 +542,9 @@ class TypesGenerator {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} includeExperimental
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function generateTestTypes(includeExperimental) {
|
||||
async function generateTestTypes() {
|
||||
const documentation = coreDocumentation.mergeWith(testDocumentation);
|
||||
const generator = new TypesGenerator({
|
||||
documentation,
|
||||
|
|
@ -574,16 +581,14 @@ class TypesGenerator {
|
|||
'TestFunction',
|
||||
]),
|
||||
doNotExportClassNames: assertionClasses,
|
||||
includeExperimental,
|
||||
});
|
||||
return await generator.generateTypes(path.join(__dirname, 'overrides-test.d.ts'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} includeExperimental
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function generateReporterTypes(includeExperimental) {
|
||||
async function generateReporterTypes() {
|
||||
const documentation = coreDocumentation.mergeWith(testDocumentation).mergeWith(reporterDocumentation);
|
||||
const generator = new TypesGenerator({
|
||||
documentation,
|
||||
|
|
@ -601,7 +606,6 @@ class TypesGenerator {
|
|||
'JSONReportTestResult',
|
||||
'JSONReportTestStep',
|
||||
]),
|
||||
includeExperimental,
|
||||
});
|
||||
return await generator.generateTypes(path.join(__dirname, 'overrides-testReporter.d.ts'));
|
||||
}
|
||||
|
|
@ -629,9 +633,9 @@ class TypesGenerator {
|
|||
if (!fs.existsSync(playwrightTypesDir))
|
||||
fs.mkdirSync(playwrightTypesDir)
|
||||
writeFile(path.join(coreTypesDir, 'protocol.d.ts'), fs.readFileSync(path.join(PROJECT_DIR, 'packages', 'playwright-core', 'src', 'server', 'chromium', 'protocol.d.ts'), 'utf8'), false);
|
||||
writeFile(path.join(coreTypesDir, 'types.d.ts'), await generateCoreTypes(false), true);
|
||||
writeFile(path.join(playwrightTypesDir, 'test.d.ts'), await generateTestTypes(false), true);
|
||||
writeFile(path.join(playwrightTypesDir, 'testReporter.d.ts'), await generateReporterTypes(false), true);
|
||||
writeFile(path.join(coreTypesDir, 'types.d.ts'), await generateCoreTypes(), true);
|
||||
writeFile(path.join(playwrightTypesDir, 'test.d.ts'), await generateTestTypes(), true);
|
||||
writeFile(path.join(playwrightTypesDir, 'testReporter.d.ts'), await generateReporterTypes(), true);
|
||||
process.exit(0);
|
||||
})().catch(e => {
|
||||
console.error(e);
|
||||
|
|
|
|||
|
|
@ -61,8 +61,6 @@ Example:
|
|||
'wk': 'webkit',
|
||||
}[args[0].toLowerCase()] ?? args[0].toLowerCase();
|
||||
const descriptors = [browsersJSON.browsers.find(b => b.name === browserName)];
|
||||
if (browserName === 'firefox')
|
||||
descriptors.push(browsersJSON.browsers.find(b => b.name === 'firefox-asan'));
|
||||
|
||||
if (!descriptors.every(d => !!d)) {
|
||||
console.log(`Unknown browser "${browserName}"`);
|
||||
|
|
|
|||
Loading…
Reference in a new issue