Compare commits
12 commits
main
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8aef8e8fc2 | ||
|
|
268942037a | ||
|
|
efc7ec1e80 | ||
|
|
43798aff92 | ||
|
|
18478e325f | ||
|
|
d19c948ce7 | ||
|
|
480ae58942 | ||
|
|
8dea604754 | ||
|
|
65859fa54b | ||
|
|
e27bc9faa8 | ||
|
|
b8949166dc | ||
|
|
59e8f4815d |
|
|
@ -244,6 +244,12 @@ Attribute name.
|
||||||
|
|
||||||
Expected attribute value.
|
Expected attribute value.
|
||||||
|
|
||||||
|
### option: LocatorAssertions.NotToHaveAttribute.ignoreCase
|
||||||
|
* since: v1.40
|
||||||
|
- `ignoreCase` <[boolean]>
|
||||||
|
|
||||||
|
Whether to perform case-insensitive match. [`option: ignoreCase`] option takes precedence over the corresponding regular expression flag if specified.
|
||||||
|
|
||||||
### option: LocatorAssertions.NotToHaveAttribute.timeout = %%-csharp-java-python-assertions-timeout-%%
|
### option: LocatorAssertions.NotToHaveAttribute.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||||
* since: v1.18
|
* since: v1.18
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,25 +36,33 @@ pwsh bin/Debug/netX/playwright.ps1 codegen demo.playwright.dev/todomvc
|
||||||
|
|
||||||
Run `codegen` and perform actions in the browser. Playwright will generate the code for the user interactions. `Codegen` will look at the rendered page and figure out the recommended locator, prioritizing role, text and test id locators. If the generator identifies multiple elements matching the locator, it will improve the locator to make it resilient and uniquely identify the target element, therefore eliminating and reducing test(s) failing and flaking due to locators.
|
Run `codegen` and perform actions in the browser. Playwright will generate the code for the user interactions. `Codegen` will look at the rendered page and figure out the recommended locator, prioritizing role, text and test id locators. If the generator identifies multiple elements matching the locator, it will improve the locator to make it resilient and uniquely identify the target element, therefore eliminating and reducing test(s) failing and flaking due to locators.
|
||||||
|
|
||||||
|
With the test generator you can record:
|
||||||
|
* Actions like click or fill by simply interacting with the page
|
||||||
|
* Assertions by clicking on one of the icons in the toolbar and then clicking on an element on the page to assert against. You can choose:
|
||||||
|
* `'assert visibility'` to assert that an element is visible
|
||||||
|
* `'assert text'` to assert that an element contains specific text
|
||||||
|
* `'assert value'` to assert that an element has a specific value
|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: js
|
* langs: js
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: java
|
* langs: java
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: python
|
* langs: python
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: csharp
|
* langs: csharp
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: js, java, python, csharp
|
* langs: js, java, python, csharp
|
||||||
|
|
||||||
|
|
@ -77,22 +85,22 @@ You can generate [locators](/locators.md) with the test generator.
|
||||||
######
|
######
|
||||||
* langs: js
|
* langs: js
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: java
|
* langs: java
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: python
|
* langs: python
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: csharp
|
* langs: csharp
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Emulation
|
### Emulation
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,17 +27,25 @@ To record a test click on the **Record new** button from the Testing sidebar. Th
|
||||||
|
|
||||||
In the browser go to the URL you wish to test and start clicking around to record your user actions.
|
In the browser go to the URL you wish to test and start clicking around to record your user actions.
|
||||||
|
|
||||||
<img width="1394" alt="clicking delete button on todo app with locator highlighted" src="https://user-images.githubusercontent.com/13063165/220957132-31b54f82-6235-4c52-a966-6863553b5b23.png" />
|

|
||||||
|
|
||||||
Playwright will record your actions and generate the test code directly in VS Code. Once you are done recording click the **cancel** button or close the browser window. You can then inspect your `test-1.spec.ts` file and see your generated test and then manually improve the test by adding [ assertions](test-assertions).
|
Playwright will record your actions and generate the test code directly in VS Code. You can also generate assertions by choosing one of the icons in the toolbar and then clicking on an element on the page to assert against. The following assertions can be generated:
|
||||||
|
* `'assert visibility'` to assert that an element is visible
|
||||||
|
* `'assert text'` to assert that an element contains specific text
|
||||||
|
* `'assert value'` to assert that an element has a specific value
|
||||||
|
|
||||||
<img width="1667" alt="vs code showing recorded actions of test" src="https://user-images.githubusercontent.com/13063165/220938674-6e1ff1d3-e75a-4238-a7fc-4c40dbc8b3bc.png" />
|

|
||||||
|
|
||||||
|
Once you are done recording click the **cancel** button or close the browser window. You can then inspect your `test-1.spec.ts` file and manually improve it if needed.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Record at Cursor
|
### Record at Cursor
|
||||||
|
|
||||||
To record from a specific point in your test move your cursor to where you want to record more actions and then click the **Record at cursor** button from the Testing sidebar. If your browser window is not already open then first run the test with 'Show browser' checked and then click the **Record at cursor** button.
|
To record from a specific point in your test move your cursor to where you want to record more actions and then click the **Record at cursor** button from the Testing sidebar. If your browser window is not already open then first run the test with 'Show browser' checked and then click the **Record at cursor** button.
|
||||||
|
|
||||||
<img width="1529" alt="record at cursor in vs code" src="https://user-images.githubusercontent.com/13063165/220959996-2bb3af59-85d9-4d58-aba7-d57375e7ca7e.png" />
|
|
||||||
|

|
||||||
|
|
||||||
In the browser window start performing the actions you want to record.
|
In the browser window start performing the actions you want to record.
|
||||||
|
|
||||||
|
|
@ -46,7 +54,7 @@ In the browser window start performing the actions you want to record.
|
||||||
|
|
||||||
In the test file in VS Code you will see your new generated actions added to your test at the cursor position.
|
In the test file in VS Code you will see your new generated actions added to your test at the cursor position.
|
||||||
|
|
||||||
<img width="1641" alt="vs code showing test code generated" src="https://user-images.githubusercontent.com/13063165/220940902-d1dbc321-0ef5-4388-9e11-6311aff59ff4.png" />
|

|
||||||
|
|
||||||
### Generating locators
|
### Generating locators
|
||||||
|
|
||||||
|
|
@ -85,25 +93,35 @@ pwsh bin/Debug/netX/playwright.ps1 codegen demo.playwright.dev/todomvc
|
||||||
|
|
||||||
Run the `codegen` command and perform actions in the browser window. Playwright will generate the code for the user interactions which you can see in the Playwright Inspector window. Once you have finished recording your test stop the recording and press the **copy** button to copy your generated test into your editor.
|
Run the `codegen` command and perform actions in the browser window. Playwright will generate the code for the user interactions which you can see in the Playwright Inspector window. Once you have finished recording your test stop the recording and press the **copy** button to copy your generated test into your editor.
|
||||||
|
|
||||||
|
With the test generator you can record:
|
||||||
|
* Actions like click or fill by simply interacting with the page
|
||||||
|
* Assertions by clicking on one of the icons in the toolbar and then clicking on an element on the page to assert against. You can choose:
|
||||||
|
* `'assert visibility'` to assert that an element is visible
|
||||||
|
* `'assert text'` to assert that an element contains specific text
|
||||||
|
* `'assert value'` to assert that an element has a specific value
|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: js
|
* langs: js
|
||||||
|
|
||||||
<img width="1365" alt="Recording a test" src="https://user-images.githubusercontent.com/13063165/212754505-b98e80fd-6dda-48f7-860b-b32b4fabee33.png" />
|

|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: java
|
* langs: java
|
||||||
|
|
||||||
<img width="1365" alt="Recording a test" src="https://user-images.githubusercontent.com/13063165/212754804-0d9f9d52-0a48-45c8-970d-e672d4a91221.png" />
|

|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: python
|
* langs: python
|
||||||
|
|
||||||
<img width="1365" alt="Recording a test" src="https://user-images.githubusercontent.com/13063165/212751993-b7da2c40-a7cc-4b13-9a91-40ee837042a1.png" />
|

|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: csharp
|
* langs: csharp
|
||||||
|
|
||||||
<img width="1365" alt="Recording a test" src="https://user-images.githubusercontent.com/13063165/212754994-fa637d81-b81d-44b8-bcd7-5dc218034f0a.png" />
|

|
||||||
|
|
||||||
|
######
|
||||||
|
* langs: js, java, python, csharp
|
||||||
|
|
||||||
When you have finished interacting with the page, press the **record** button to stop the recording and use the **copy** button to copy the generated code to your editor.
|
When you have finished interacting with the page, press the **record** button to stop the recording and use the **copy** button to copy the generated code to your editor.
|
||||||
|
|
||||||
|
|
@ -122,22 +140,22 @@ You can generate [locators](/locators.md) with the test generator.
|
||||||
######
|
######
|
||||||
* langs: js
|
* langs: js
|
||||||
|
|
||||||
<img width="1321" alt="Picking a locator" src="https://user-images.githubusercontent.com/13063165/212753129-55fbcf69-0be3-422e-888a-f52060c7aa6b.png" />
|

|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: java
|
* langs: java
|
||||||
|
|
||||||
<img width="1321" alt="Picking a locator" src="https://user-images.githubusercontent.com/13063165/212753446-456484a8-8c37-4104-8db5-4525b74c8cf1.png" />
|

|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: python
|
* langs: python
|
||||||
|
|
||||||
<img width="1321" alt="Picking a locator" src="https://user-images.githubusercontent.com/13063165/212753605-861d66a4-fc1c-4559-b821-cb1f39059337.png" />
|

|
||||||
|
|
||||||
######
|
######
|
||||||
* langs: csharp
|
* langs: csharp
|
||||||
|
|
||||||
<img width="1321" alt="Picking a locator" src="https://user-images.githubusercontent.com/13063165/212753728-49d35a7c-c05a-4298-bf66-89930d2cb578.png" />
|

|
||||||
|
|
||||||
## Emulation
|
## Emulation
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -145,23 +145,28 @@ CodeGen will auto generate your tests for you as you perform actions in the brow
|
||||||
|
|
||||||
### Record a New Test
|
### Record a New Test
|
||||||
|
|
||||||
To record a test click on the **Record new** button from the Testing sidebar. This will create a `test-1.spec.ts` file as well as open up a browser window. In the browser go to the URL you wish to test and start clicking around. Playwright will record your actions and generate a test for you. Once you are done recording click the **cancel** button or close the browser window. You can then inspect your `test-1.spec.ts` file and see your generated test.
|
To record a test click on the **Record new** button from the Testing sidebar. This will create a `test-1.spec.ts` file as well as open up a browser window. In the browser go to the URL you wish to test and start clicking around. Playwright will record your actions and generate the test code directly in VS Code. You can also generate assertions by choosing one of the icons in the toolbar and then clicking on an element on the page to assert against. The following assertions can be generated:
|
||||||
|
* `'assert visibility'` to assert that an element is visible
|
||||||
|
* `'assert text'` to assert that an element contains specific text
|
||||||
|
* `'assert value'` to assert that an element has a specific value
|
||||||
|
|
||||||
|
Once you are done recording click the **cancel** button or close the browser window. You can then inspect your `test-1.spec.ts` file and see your generated test.
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Record at Cursor
|
### Record at Cursor
|
||||||
|
|
||||||
To record from a specific point in your test file click the **Record at cursor** button from the Testing sidebar. This generates actions into the existing test at the current cursor position. You can run the test, position the cursor at the end of the test and continue generating the test.
|
To record from a specific point in your test file click the **Record at cursor** button from the Testing sidebar. This generates actions into the existing test at the current cursor position. You can run the test, position the cursor at the end of the test and continue generating the test.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Picking a Locator
|
### Picking a Locator
|
||||||
|
|
||||||
Pick a [locator](./locators.md) and copy it into your test file by clicking the **Pick locator** button form the testing sidebar. Then in the browser click the element you require and it will now show up in the **Pick locator** box in VS Code. Press 'enter' on your keyboard to copy the locator into the clipboard and then paste anywhere in your code. Or press 'escape' if you want to cancel.
|
Pick a [locator](./locators.md) and copy it into your test file by clicking the **Pick locator** button form the testing sidebar. Then in the browser click the element you require and it will now show up in the **Pick locator** box in VS Code. Press 'enter' on your keyboard to copy the locator into the clipboard and then paste anywhere in your code. Or press 'escape' if you want to cancel.
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Playwright will look at your page and figure out the best locator, prioritizing [role, text and test id locators](./locators.md). If the generator finds multiple elements matching the locator, it will improve the locator to make it resilient and uniquely identify the target element, so you don't have to worry about failing tests due to locators.
|
Playwright will look at your page and figure out the best locator, prioritizing [role, text and test id locators](./locators.md). If the generator finds multiple elements matching the locator, it will improve the locator to make it resilient and uniquely identify the target element, so you don't have to worry about failing tests due to locators.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ toc_max_heading_level: 2
|
||||||
|
|
||||||
### Test Generator Update
|
### Test Generator Update
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
New tools to generate assertions:
|
New tools to generate assertions:
|
||||||
- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`].
|
- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`].
|
||||||
|
|
@ -35,7 +35,7 @@ await Expect(Page.GetByPlaceholder("Search docs")).ToHaveValueAsync("locator");
|
||||||
### Other Changes
|
### Other Changes
|
||||||
|
|
||||||
- Methods [`method: Download.path`] and [`method: Download.createReadStream`] throw an error for failed and cancelled downloads.
|
- Methods [`method: Download.path`] and [`method: Download.createReadStream`] throw an error for failed and cancelled downloads.
|
||||||
- Playwright [docker image](./docker.md) now comes with Node.js v20.
|
- Playwright [docker image](./docker.md) now comes with .NET 8 (new LTS).
|
||||||
|
|
||||||
### Browser Versions
|
### Browser Versions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ toc_max_heading_level: 2
|
||||||
|
|
||||||
### Test Generator Update
|
### Test Generator Update
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
New tools to generate assertions:
|
New tools to generate assertions:
|
||||||
- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`].
|
- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`].
|
||||||
|
|
@ -35,7 +35,6 @@ assertThat(page.getByPlaceholder("Search docs")).hasValue("locator");
|
||||||
### Other Changes
|
### Other Changes
|
||||||
|
|
||||||
- Methods [`method: Download.path`] and [`method: Download.createReadStream`] throw an error for failed and cancelled downloads.
|
- Methods [`method: Download.path`] and [`method: Download.createReadStream`] throw an error for failed and cancelled downloads.
|
||||||
- Playwright [docker image](./docker.md) now comes with Node.js v20.
|
|
||||||
|
|
||||||
### Browser Versions
|
### Browser Versions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,14 @@ import LiteYouTube from '@site/src/components/LiteYouTube';
|
||||||
|
|
||||||
## Version 1.40
|
## Version 1.40
|
||||||
|
|
||||||
|
<LiteYouTube
|
||||||
|
id="mn892dV81_8"
|
||||||
|
title="Playwright 1.40"
|
||||||
|
/>
|
||||||
|
|
||||||
### Test Generator Update
|
### Test Generator Update
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
New tools to generate assertions:
|
New tools to generate assertions:
|
||||||
- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`].
|
- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`].
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ toc_max_heading_level: 2
|
||||||
|
|
||||||
### Test Generator Update
|
### Test Generator Update
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
New tools to generate assertions:
|
New tools to generate assertions:
|
||||||
- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`].
|
- "Assert visibility" tool generates [`method: LocatorAssertions.toBeVisible`].
|
||||||
|
|
@ -38,7 +38,6 @@ def test_example(page: Page) -> None:
|
||||||
### Other Changes
|
### Other Changes
|
||||||
|
|
||||||
- Method [`method: Download.path`] throws an error for failed and cancelled downloads.
|
- Method [`method: Download.path`] throws an error for failed and cancelled downloads.
|
||||||
- Playwright [docker image](./docker.md) now comes with Node.js v20.
|
|
||||||
|
|
||||||
### Browser Versions
|
### Browser Versions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ Options for tracing are:
|
||||||
|
|
||||||
This will record the trace and place it into the file named `trace.zip` in your `test-results` directory.
|
This will record the trace and place it into the file named `trace.zip` in your `test-results` directory.
|
||||||
|
|
||||||
<details><summary>If you are not using Pytest, click here to learn how to record traces.
|
<details>
|
||||||
</summary>
|
<summary>If you are not using Pytest, click here to learn how to record traces.</summary>
|
||||||
|
|
||||||
```python async
|
```python async
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
|
|
|
||||||
|
|
@ -177,8 +177,8 @@ Options for tracing are:
|
||||||
|
|
||||||
This will record the trace and place it into the file named `trace.zip` in your `test-results` directory.
|
This will record the trace and place it into the file named `trace.zip` in your `test-results` directory.
|
||||||
|
|
||||||
<details><summary>If you are not using Pytest, click here to learn how to record traces.
|
<details>
|
||||||
</summary>
|
<summary>If you are not using Pytest, click here to learn how to record traces.</summary>
|
||||||
|
|
||||||
```python async
|
```python async
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
|
|
|
||||||
100
package-lock.json
generated
100
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "playwright-internal",
|
"name": "playwright-internal",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "playwright-internal",
|
"name": "playwright-internal",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
@ -6976,10 +6976,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/playwright": {
|
"packages/playwright": {
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
|
|
@ -6993,11 +6993,11 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-browser-chromium": {
|
"packages/playwright-browser-chromium": {
|
||||||
"name": "@playwright/browser-chromium",
|
"name": "@playwright/browser-chromium",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
|
|
@ -7005,11 +7005,11 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-browser-firefox": {
|
"packages/playwright-browser-firefox": {
|
||||||
"name": "@playwright/browser-firefox",
|
"name": "@playwright/browser-firefox",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
|
|
@ -7017,22 +7017,22 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-browser-webkit": {
|
"packages/playwright-browser-webkit": {
|
||||||
"name": "@playwright/browser-webkit",
|
"name": "@playwright/browser-webkit",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/playwright-chromium": {
|
"packages/playwright-chromium": {
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
|
|
@ -7042,7 +7042,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/playwright-core": {
|
"packages/playwright-core": {
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-core": "cli.js"
|
"playwright-core": "cli.js"
|
||||||
|
|
@ -7053,11 +7053,11 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-core": {
|
"packages/playwright-ct-core": {
|
||||||
"name": "@playwright/experimental-ct-core",
|
"name": "@playwright/experimental-ct-core",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.40.0-next",
|
"playwright": "1.40.1",
|
||||||
"playwright-core": "1.40.0-next",
|
"playwright-core": "1.40.1",
|
||||||
"vite": "^4.4.10"
|
"vite": "^4.4.10"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -7069,10 +7069,10 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-react": {
|
"packages/playwright-ct-react": {
|
||||||
"name": "@playwright/experimental-ct-react",
|
"name": "@playwright/experimental-ct-react",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@vitejs/plugin-react": "^4.0.0"
|
"@vitejs/plugin-react": "^4.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -7101,10 +7101,10 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-react17": {
|
"packages/playwright-ct-react17": {
|
||||||
"name": "@playwright/experimental-ct-react17",
|
"name": "@playwright/experimental-ct-react17",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@vitejs/plugin-react": "^4.0.0"
|
"@vitejs/plugin-react": "^4.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -7133,10 +7133,10 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-solid": {
|
"packages/playwright-ct-solid": {
|
||||||
"name": "@playwright/experimental-ct-solid",
|
"name": "@playwright/experimental-ct-solid",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"vite-plugin-solid": "^2.7.0"
|
"vite-plugin-solid": "^2.7.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -7151,10 +7151,10 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-svelte": {
|
"packages/playwright-ct-svelte": {
|
||||||
"name": "@playwright/experimental-ct-svelte",
|
"name": "@playwright/experimental-ct-svelte",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.1.1"
|
"@sveltejs/vite-plugin-svelte": "^2.1.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -7169,10 +7169,10 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-vue": {
|
"packages/playwright-ct-vue": {
|
||||||
"name": "@playwright/experimental-ct-vue",
|
"name": "@playwright/experimental-ct-vue",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@vitejs/plugin-vue": "^4.2.1"
|
"@vitejs/plugin-vue": "^4.2.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -7220,10 +7220,10 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-vue2": {
|
"packages/playwright-ct-vue2": {
|
||||||
"name": "@playwright/experimental-ct-vue2",
|
"name": "@playwright/experimental-ct-vue2",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@vitejs/plugin-vue2": "^2.2.0"
|
"@vitejs/plugin-vue2": "^2.2.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -7237,11 +7237,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/playwright-firefox": {
|
"packages/playwright-firefox": {
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
|
|
@ -7275,10 +7275,10 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-test": {
|
"packages/playwright-test": {
|
||||||
"name": "@playwright/test",
|
"name": "@playwright/test",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.40.0-next"
|
"playwright": "1.40.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
|
|
@ -7288,11 +7288,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/playwright-webkit": {
|
"packages/playwright-webkit": {
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
|
|
@ -8032,33 +8032,33 @@
|
||||||
"@playwright/browser-chromium": {
|
"@playwright/browser-chromium": {
|
||||||
"version": "file:packages/playwright-browser-chromium",
|
"version": "file:packages/playwright-browser-chromium",
|
||||||
"requires": {
|
"requires": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@playwright/browser-firefox": {
|
"@playwright/browser-firefox": {
|
||||||
"version": "file:packages/playwright-browser-firefox",
|
"version": "file:packages/playwright-browser-firefox",
|
||||||
"requires": {
|
"requires": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@playwright/browser-webkit": {
|
"@playwright/browser-webkit": {
|
||||||
"version": "file:packages/playwright-browser-webkit",
|
"version": "file:packages/playwright-browser-webkit",
|
||||||
"requires": {
|
"requires": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@playwright/experimental-ct-core": {
|
"@playwright/experimental-ct-core": {
|
||||||
"version": "file:packages/playwright-ct-core",
|
"version": "file:packages/playwright-ct-core",
|
||||||
"requires": {
|
"requires": {
|
||||||
"playwright": "1.40.0-next",
|
"playwright": "1.40.1",
|
||||||
"playwright-core": "1.40.0-next",
|
"playwright-core": "1.40.1",
|
||||||
"vite": "^4.4.10"
|
"vite": "^4.4.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@playwright/experimental-ct-react": {
|
"@playwright/experimental-ct-react": {
|
||||||
"version": "file:packages/playwright-ct-react",
|
"version": "file:packages/playwright-ct-react",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@vitejs/plugin-react": "^4.0.0"
|
"@vitejs/plugin-react": "^4.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -8078,7 +8078,7 @@
|
||||||
"@playwright/experimental-ct-react17": {
|
"@playwright/experimental-ct-react17": {
|
||||||
"version": "file:packages/playwright-ct-react17",
|
"version": "file:packages/playwright-ct-react17",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@vitejs/plugin-react": "^4.0.0"
|
"@vitejs/plugin-react": "^4.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -8098,7 +8098,7 @@
|
||||||
"@playwright/experimental-ct-solid": {
|
"@playwright/experimental-ct-solid": {
|
||||||
"version": "file:packages/playwright-ct-solid",
|
"version": "file:packages/playwright-ct-solid",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"solid-js": "^1.7.0",
|
"solid-js": "^1.7.0",
|
||||||
"vite-plugin-solid": "^2.7.0"
|
"vite-plugin-solid": "^2.7.0"
|
||||||
}
|
}
|
||||||
|
|
@ -8106,7 +8106,7 @@
|
||||||
"@playwright/experimental-ct-svelte": {
|
"@playwright/experimental-ct-svelte": {
|
||||||
"version": "file:packages/playwright-ct-svelte",
|
"version": "file:packages/playwright-ct-svelte",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.1.1",
|
"@sveltejs/vite-plugin-svelte": "^2.1.1",
|
||||||
"svelte": "^3.55.1"
|
"svelte": "^3.55.1"
|
||||||
}
|
}
|
||||||
|
|
@ -8114,7 +8114,7 @@
|
||||||
"@playwright/experimental-ct-vue": {
|
"@playwright/experimental-ct-vue": {
|
||||||
"version": "file:packages/playwright-ct-vue",
|
"version": "file:packages/playwright-ct-vue",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@vitejs/plugin-vue": "^4.2.1"
|
"@vitejs/plugin-vue": "^4.2.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -8148,7 +8148,7 @@
|
||||||
"@playwright/experimental-ct-vue2": {
|
"@playwright/experimental-ct-vue2": {
|
||||||
"version": "file:packages/playwright-ct-vue2",
|
"version": "file:packages/playwright-ct-vue2",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@vitejs/plugin-vue2": "^2.2.0",
|
"@vitejs/plugin-vue2": "^2.2.0",
|
||||||
"vue": "^2.7.14"
|
"vue": "^2.7.14"
|
||||||
}
|
}
|
||||||
|
|
@ -8156,7 +8156,7 @@
|
||||||
"@playwright/test": {
|
"@playwright/test": {
|
||||||
"version": "file:packages/playwright-test",
|
"version": "file:packages/playwright-test",
|
||||||
"requires": {
|
"requires": {
|
||||||
"playwright": "1.40.0-next"
|
"playwright": "1.40.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@sindresorhus/is": {
|
"@sindresorhus/is": {
|
||||||
|
|
@ -11000,13 +11000,13 @@
|
||||||
"version": "file:packages/playwright",
|
"version": "file:packages/playwright",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fsevents": "2.3.2",
|
"fsevents": "2.3.2",
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"playwright-chromium": {
|
"playwright-chromium": {
|
||||||
"version": "file:packages/playwright-chromium",
|
"version": "file:packages/playwright-chromium",
|
||||||
"requires": {
|
"requires": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"playwright-core": {
|
"playwright-core": {
|
||||||
|
|
@ -11015,13 +11015,13 @@
|
||||||
"playwright-firefox": {
|
"playwright-firefox": {
|
||||||
"version": "file:packages/playwright-firefox",
|
"version": "file:packages/playwright-firefox",
|
||||||
"requires": {
|
"requires": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"playwright-webkit": {
|
"playwright-webkit": {
|
||||||
"version": "file:packages/playwright-webkit",
|
"version": "file:packages/playwright-webkit",
|
||||||
"requires": {
|
"requires": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "playwright-internal",
|
"name": "playwright-internal",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "A high-level API to automate web browsers",
|
"description": "A high-level API to automate web browsers",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/browser-chromium",
|
"name": "@playwright/browser-chromium",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "Playwright package that automatically installs Chromium",
|
"description": "Playwright package that automatically installs Chromium",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -27,6 +27,6 @@
|
||||||
"install": "node install.js"
|
"install": "node install.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/browser-firefox",
|
"name": "@playwright/browser-firefox",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "Playwright package that automatically installs Firefox",
|
"description": "Playwright package that automatically installs Firefox",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -27,6 +27,6 @@
|
||||||
"install": "node install.js"
|
"install": "node install.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/browser-webkit",
|
"name": "@playwright/browser-webkit",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "Playwright package that automatically installs WebKit",
|
"description": "Playwright package that automatically installs WebKit",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -27,6 +27,6 @@
|
||||||
"install": "node install.js"
|
"install": "node install.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "playwright-chromium",
|
"name": "playwright-chromium",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "A high-level API to automate Chromium",
|
"description": "A high-level API to automate Chromium",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -30,6 +30,6 @@
|
||||||
"install": "node install.js"
|
"install": "node install.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "playwright-core",
|
"name": "playwright-core",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "A high-level API to automate web browsers",
|
"description": "A high-level API to automate web browsers",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ import type { Protocol } from './protocol';
|
||||||
import { VideoRecorder } from './videoRecorder';
|
import { VideoRecorder } from './videoRecorder';
|
||||||
import { BrowserContext } from '../browserContext';
|
import { BrowserContext } from '../browserContext';
|
||||||
import { TargetClosedError } from '../errors';
|
import { TargetClosedError } from '../errors';
|
||||||
|
import { isSessionClosedError } from '../protocolError';
|
||||||
|
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||||
|
|
@ -132,7 +133,7 @@ export class CRPage implements PageDelegate {
|
||||||
return cb(frameSession);
|
return cb(frameSession);
|
||||||
return cb(frameSession).catch(e => {
|
return cb(frameSession).catch(e => {
|
||||||
// Broadcasting a message to the closed iframe should be a noop.
|
// Broadcasting a message to the closed iframe should be a noop.
|
||||||
if (e.message && e.message.includes('Target closed'))
|
if (isSessionClosedError(e))
|
||||||
return;
|
return;
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,10 @@ x-pw-dialog {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 500px;
|
width: 400px;
|
||||||
height: 200px;
|
height: 150px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
x-pw-dialog-body {
|
x-pw-dialog-body {
|
||||||
|
|
@ -217,6 +218,15 @@ x-pw-tool-item.cancel > x-div {
|
||||||
mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>");
|
mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
x-pw-tool-item.succeeded > x-div {
|
||||||
|
/* codicon: pass */
|
||||||
|
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M6.27 10.87h.71l4.56-4.56-.71-.71-4.2 4.21-1.92-1.92L4 8.6l2.27 2.27z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M8.6 1c1.6.1 3.1.9 4.2 2 1.3 1.4 2 3.1 2 5.1 0 1.6-.6 3.1-1.6 4.4-1 1.2-2.4 2.1-4 2.4-1.6.3-3.2.1-4.6-.7-1.4-.8-2.5-2-3.1-3.5C.9 9.2.8 7.5 1.3 6c.5-1.6 1.4-2.9 2.8-3.8C5.4 1.3 7 .9 8.6 1zm.5 12.9c1.3-.3 2.5-1 3.4-2.1.8-1.1 1.3-2.4 1.2-3.8 0-1.6-.6-3.2-1.7-4.3-1-1-2.2-1.6-3.6-1.7-1.3-.1-2.7.2-3.8 1-1.1.8-1.9 1.9-2.3 3.3-.4 1.3-.4 2.7.2 4 .6 1.3 1.5 2.3 2.7 3 1.2.7 2.6.9 3.9.6z'/></svg>") !important;
|
||||||
|
mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M6.27 10.87h.71l4.56-4.56-.71-.71-4.2 4.21-1.92-1.92L4 8.6l2.27 2.27z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M8.6 1c1.6.1 3.1.9 4.2 2 1.3 1.4 2 3.1 2 5.1 0 1.6-.6 3.1-1.6 4.4-1 1.2-2.4 2.1-4 2.4-1.6.3-3.2.1-4.6-.7-1.4-.8-2.5-2-3.1-3.5C.9 9.2.8 7.5 1.3 6c.5-1.6 1.4-2.9 2.8-3.8C5.4 1.3 7 .9 8.6 1zm.5 12.9c1.3-.3 2.5-1 3.4-2.1.8-1.1 1.3-2.4 1.2-3.8 0-1.6-.6-3.2-1.7-4.3-1-1-2.2-1.6-3.6-1.7-1.3-.1-2.7.2-3.8 1-1.1.8-1.9 1.9-2.3 3.3-.4 1.3-.4 2.7.2 4 .6 1.3 1.5 2.3 2.7 3 1.2.7 2.6.9 3.9.6z'/></svg>") !important;
|
||||||
|
background-color: #388a34 !important;
|
||||||
|
-webkit-mask-size: 18px !important;
|
||||||
|
mask-size: 18px !important;
|
||||||
|
}
|
||||||
|
|
||||||
x-pw-overlay {
|
x-pw-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
@ -238,13 +248,15 @@ x-pw-overlay x-pw-tool-item {
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea.text-editor {
|
textarea.text-editor {
|
||||||
font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif;
|
font-family: system-ui,Ubuntu,Droid Sans,sans-serif;
|
||||||
flex: auto;
|
flex: auto;
|
||||||
border: none;
|
border: none;
|
||||||
margin: 6px;
|
margin: 6px 10px;
|
||||||
color: #333;
|
color: #333;
|
||||||
outline: 1px solid transparent!important;
|
outline: 1px solid transparent!important;
|
||||||
resize: none;
|
resize: none;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea.text-editor.does-not-match {
|
textarea.text-editor.does-not-match {
|
||||||
|
|
|
||||||
|
|
@ -24,28 +24,8 @@ import { isInsideScope } from './domUtils';
|
||||||
import { elementText } from './selectorUtils';
|
import { elementText } from './selectorUtils';
|
||||||
import type { ElementText } from './selectorUtils';
|
import type { ElementText } from './selectorUtils';
|
||||||
import { asLocator } from '../../utils/isomorphic/locatorGenerators';
|
import { asLocator } from '../../utils/isomorphic/locatorGenerators';
|
||||||
import type { Language } from '../../utils/isomorphic/locatorGenerators';
|
|
||||||
import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser';
|
|
||||||
import { parseSelector } from '@isomorphic/selectorParser';
|
|
||||||
import { normalizeWhiteSpace } from '@isomorphic/stringUtils';
|
import { normalizeWhiteSpace } from '@isomorphic/stringUtils';
|
||||||
|
|
||||||
// @ts-ignore @no-check-deps
|
|
||||||
import CodeMirrorImpl from 'codemirror-shadow-1';
|
|
||||||
import type CodeMirrorType from 'codemirror';
|
|
||||||
// @no-check-deps
|
|
||||||
import codemirrorCSS from 'codemirror-shadow-1/lib/codemirror.css?inline';
|
|
||||||
// @no-check-deps
|
|
||||||
import 'codemirror-shadow-1/mode/css/css';
|
|
||||||
// @no-check-deps
|
|
||||||
import 'codemirror-shadow-1/mode/htmlmixed/htmlmixed';
|
|
||||||
// @no-check-deps
|
|
||||||
import 'codemirror-shadow-1/mode/javascript/javascript';
|
|
||||||
// @no-check-deps
|
|
||||||
import 'codemirror-shadow-1/mode/python/python';
|
|
||||||
// @no-check-deps
|
|
||||||
import 'codemirror-shadow-1/mode/clike/clike';
|
|
||||||
const CodeMirror = CodeMirrorImpl as typeof CodeMirrorType;
|
|
||||||
|
|
||||||
interface RecorderDelegate {
|
interface RecorderDelegate {
|
||||||
performAction?(action: actions.Action): Promise<void>;
|
performAction?(action: actions.Action): Promise<void>;
|
||||||
recordAction?(action: actions.Action): Promise<void>;
|
recordAction?(action: actions.Action): Promise<void>;
|
||||||
|
|
@ -68,6 +48,7 @@ interface RecorderTool {
|
||||||
onMouseDown?(event: MouseEvent): void;
|
onMouseDown?(event: MouseEvent): void;
|
||||||
onMouseUp?(event: MouseEvent): void;
|
onMouseUp?(event: MouseEvent): void;
|
||||||
onMouseMove?(event: MouseEvent): void;
|
onMouseMove?(event: MouseEvent): void;
|
||||||
|
onMouseEnter?(event: MouseEvent): void;
|
||||||
onMouseLeave?(event: MouseEvent): void;
|
onMouseLeave?(event: MouseEvent): void;
|
||||||
onFocus?(event: Event): void;
|
onFocus?(event: Event): void;
|
||||||
onScroll?(event: Event): void;
|
onScroll?(event: Event): void;
|
||||||
|
|
@ -109,6 +90,7 @@ class InspectTool implements RecorderTool {
|
||||||
signals: [],
|
signals: [],
|
||||||
});
|
});
|
||||||
this._recorder.delegate.setMode?.('recording');
|
this._recorder.delegate.setMode?.('recording');
|
||||||
|
this._recorder.overlay?.flashToolSucceeded('assertingVisibility');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._recorder.delegate.setSelector?.(this._hoveredModel ? this._hoveredModel.selector : '');
|
this._recorder.delegate.setSelector?.(this._hoveredModel ? this._hoveredModel.selector : '');
|
||||||
|
|
@ -146,6 +128,10 @@ class InspectTool implements RecorderTool {
|
||||||
this._recorder.updateHighlight(model, true, { color: this._assertVisibility ? '#8acae480' : undefined });
|
this._recorder.updateHighlight(model, true, { color: this._assertVisibility ? '#8acae480' : undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMouseEnter(event: MouseEvent) {
|
||||||
|
consumeEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
onMouseLeave(event: MouseEvent) {
|
onMouseLeave(event: MouseEvent) {
|
||||||
consumeEvent(event);
|
consumeEvent(event);
|
||||||
const window = this._recorder.injectedScript.window;
|
const window = this._recorder.injectedScript.window;
|
||||||
|
|
@ -518,14 +504,23 @@ class TextAssertionTool implements RecorderTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick(event: MouseEvent) {
|
onClick(event: MouseEvent) {
|
||||||
|
consumeEvent(event);
|
||||||
|
if (this._kind === 'value') {
|
||||||
|
const action = this._generateAction();
|
||||||
|
if (action) {
|
||||||
|
this._recorder.delegate.recordAction?.(action);
|
||||||
|
this._recorder.delegate.setMode?.('recording');
|
||||||
|
this._recorder.overlay?.flashToolSucceeded('assertingValue');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (!this._dialogElement)
|
if (!this._dialogElement)
|
||||||
this._showDialog();
|
this._showDialog();
|
||||||
consumeEvent(event);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseDown(event: MouseEvent) {
|
onMouseDown(event: MouseEvent) {
|
||||||
const target = this._recorder.deepEventTarget(event);
|
const target = this._recorder.deepEventTarget(event);
|
||||||
if (target.nodeName === 'SELECT')
|
if (this._elementHasValue(target))
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -618,7 +613,7 @@ class TextAssertionTool implements RecorderTool {
|
||||||
if (!this._hoverHighlight?.elements[0])
|
if (!this._hoverHighlight?.elements[0])
|
||||||
return;
|
return;
|
||||||
this._action = this._generateAction();
|
this._action = this._generateAction();
|
||||||
if (!this._action)
|
if (!this._action || this._action.name !== 'assertText')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._dialogElement = this._recorder.document.createElement('x-pw-dialog');
|
this._dialogElement = this._recorder.document.createElement('x-pw-dialog');
|
||||||
|
|
@ -636,50 +631,17 @@ class TextAssertionTool implements RecorderTool {
|
||||||
|
|
||||||
this._recorder.document.addEventListener('keydown', this._keyboardListener, true);
|
this._recorder.document.addEventListener('keydown', this._keyboardListener, true);
|
||||||
const toolbarElement = this._recorder.document.createElement('x-pw-tools-list');
|
const toolbarElement = this._recorder.document.createElement('x-pw-tools-list');
|
||||||
toolbarElement.appendChild(this._createLabel(this._action));
|
const labelElement = this._recorder.document.createElement('label');
|
||||||
|
labelElement.textContent = 'Assert that element contains text';
|
||||||
|
toolbarElement.appendChild(labelElement);
|
||||||
toolbarElement.appendChild(this._recorder.document.createElement('x-spacer'));
|
toolbarElement.appendChild(this._recorder.document.createElement('x-spacer'));
|
||||||
toolbarElement.appendChild(this._acceptButton);
|
toolbarElement.appendChild(this._acceptButton);
|
||||||
toolbarElement.appendChild(this._cancelButton);
|
toolbarElement.appendChild(this._cancelButton);
|
||||||
|
|
||||||
this._dialogElement.appendChild(toolbarElement);
|
this._dialogElement.appendChild(toolbarElement);
|
||||||
const bodyElement = this._recorder.document.createElement('x-pw-dialog-body');
|
const bodyElement = this._recorder.document.createElement('x-pw-dialog-body');
|
||||||
const cmStyle = this._recorder.document.createElement('style');
|
|
||||||
const cmElement = this._recorder.document.createElement('x-locator-editor');
|
|
||||||
cmStyle.textContent = codemirrorCSS;
|
|
||||||
bodyElement.appendChild(cmStyle);
|
|
||||||
bodyElement.appendChild(cmElement);
|
|
||||||
const cm = CodeMirror(cmElement, {
|
|
||||||
value: asLocator(this._recorder.state.language, this._action.selector),
|
|
||||||
mode: cmModeForLanguage(this._recorder.state.language),
|
|
||||||
readOnly: false,
|
|
||||||
lineNumbers: false,
|
|
||||||
lineWrapping: true,
|
|
||||||
});
|
|
||||||
cm.on('keydown', (_, event) => {
|
|
||||||
if (event.key === 'Tab')
|
|
||||||
(event as any).codemirrorIgnore = true;
|
|
||||||
});
|
|
||||||
cm.on('change', () => {
|
|
||||||
if (this._action) {
|
|
||||||
const selector = locatorOrSelectorAsSelector(this._recorder.state.language, cm.getValue(), this._recorder.state.testIdAttributeName);
|
|
||||||
let elements: Element[] = [];
|
|
||||||
try {
|
|
||||||
elements = this._recorder.injectedScript.querySelectorAll(parseSelector(selector), this._recorder.document);
|
|
||||||
} catch {
|
|
||||||
}
|
|
||||||
cmElement.classList.toggle('does-not-match', !elements.length);
|
|
||||||
this._hoverHighlight = elements.length ? {
|
|
||||||
selector,
|
|
||||||
elements,
|
|
||||||
} : null;
|
|
||||||
this._action.selector = selector;
|
|
||||||
this._recorder.updateHighlight(this._hoverHighlight, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let elementToFocus: HTMLElement | null = null;
|
|
||||||
const action = this._action;
|
const action = this._action;
|
||||||
if (action.name === 'assertText') {
|
|
||||||
const textElement = this._recorder.document.createElement('textarea');
|
const textElement = this._recorder.document.createElement('textarea');
|
||||||
textElement.setAttribute('spellcheck', 'false');
|
textElement.setAttribute('spellcheck', 'false');
|
||||||
textElement.value = this._renderValue(this._action);
|
textElement.value = this._renderValue(this._action);
|
||||||
|
|
@ -692,66 +654,18 @@ class TextAssertionTool implements RecorderTool {
|
||||||
return;
|
return;
|
||||||
action.text = newValue;
|
action.text = newValue;
|
||||||
const targetText = normalizeWhiteSpace(elementText(this._textCache, target).full);
|
const targetText = normalizeWhiteSpace(elementText(this._textCache, target).full);
|
||||||
const matches = action.substring ? newValue && targetText.includes(newValue) : targetText === newValue;
|
const matches = newValue && targetText.includes(newValue);
|
||||||
textElement.classList.toggle('does-not-match', !matches);
|
textElement.classList.toggle('does-not-match', !matches);
|
||||||
};
|
};
|
||||||
textElement.addEventListener('input', updateAndValidate);
|
textElement.addEventListener('input', updateAndValidate);
|
||||||
bodyElement.appendChild(textElement);
|
bodyElement.appendChild(textElement);
|
||||||
|
|
||||||
// Add a toolbar substring checkbox.
|
|
||||||
const substringElement = this._recorder.document.createElement('label');
|
|
||||||
substringElement.style.cursor = 'pointer';
|
|
||||||
const checkboxElement = this._recorder.document.createElement('input');
|
|
||||||
substringElement.appendChild(checkboxElement);
|
|
||||||
substringElement.appendChild(this._recorder.document.createTextNode('Substring'));
|
|
||||||
checkboxElement.type = 'checkbox';
|
|
||||||
checkboxElement.style.cursor = 'pointer';
|
|
||||||
checkboxElement.checked = action.substring;
|
|
||||||
checkboxElement.addEventListener('change', () => {
|
|
||||||
action.substring = checkboxElement.checked;
|
|
||||||
updateAndValidate();
|
|
||||||
});
|
|
||||||
toolbarElement.insertBefore(substringElement, this._acceptButton);
|
|
||||||
|
|
||||||
elementToFocus = textElement;
|
|
||||||
} else if (action.name === 'assertValue') {
|
|
||||||
const textElement = this._recorder.document.createElement('textarea');
|
|
||||||
textElement.setAttribute('spellcheck', 'false');
|
|
||||||
textElement.value = this._renderValue(this._action);
|
|
||||||
textElement.classList.add('text-editor');
|
|
||||||
|
|
||||||
textElement.addEventListener('input', () => {
|
|
||||||
action.value = textElement.value;
|
|
||||||
});
|
|
||||||
bodyElement.appendChild(textElement);
|
|
||||||
elementToFocus = textElement;
|
|
||||||
} else if (action.name === 'assertChecked') {
|
|
||||||
const labelElement = this._recorder.document.createElement('label');
|
|
||||||
labelElement.textContent = 'Value:';
|
|
||||||
const checkboxElement = this._recorder.document.createElement('input');
|
|
||||||
labelElement.appendChild(checkboxElement);
|
|
||||||
checkboxElement.type = 'checkbox';
|
|
||||||
checkboxElement.checked = action.checked;
|
|
||||||
checkboxElement.addEventListener('change', () => {
|
|
||||||
action.checked = checkboxElement.checked;
|
|
||||||
});
|
|
||||||
bodyElement.appendChild(labelElement);
|
|
||||||
elementToFocus = labelElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._dialogElement.appendChild(bodyElement);
|
this._dialogElement.appendChild(bodyElement);
|
||||||
this._recorder.highlight.appendChild(this._dialogElement);
|
this._recorder.highlight.appendChild(this._dialogElement);
|
||||||
const position = this._recorder.highlight.tooltipPosition(this._recorder.highlight.firstBox()!, this._dialogElement);
|
const position = this._recorder.highlight.tooltipPosition(this._recorder.highlight.firstBox()!, this._dialogElement);
|
||||||
this._dialogElement.style.top = position.anchorTop + 'px';
|
this._dialogElement.style.top = position.anchorTop + 'px';
|
||||||
this._dialogElement.style.left = position.anchorLeft + 'px';
|
this._dialogElement.style.left = position.anchorLeft + 'px';
|
||||||
elementToFocus?.focus();
|
textElement.focus();
|
||||||
cm.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createLabel(action: actions.AssertAction) {
|
|
||||||
const labelElement = this._recorder.document.createElement('label');
|
|
||||||
labelElement.textContent = action.name === 'assertText' ? 'Assert text' : action.name === 'assertValue' ? 'Assert value' : 'Assert checked';
|
|
||||||
return labelElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _closeDialog() {
|
private _closeDialog() {
|
||||||
|
|
@ -829,7 +743,7 @@ class Overlay {
|
||||||
toolsListElement.appendChild(this._assertVisibilityToggle);
|
toolsListElement.appendChild(this._assertVisibilityToggle);
|
||||||
|
|
||||||
this._assertTextToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item');
|
this._assertTextToggle = this._recorder.injectedScript.document.createElement('x-pw-tool-item');
|
||||||
this._assertTextToggle.title = 'Assert text and values';
|
this._assertTextToggle.title = 'Assert text';
|
||||||
this._assertTextToggle.classList.add('text');
|
this._assertTextToggle.classList.add('text');
|
||||||
this._assertTextToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div'));
|
this._assertTextToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div'));
|
||||||
this._assertTextToggle.addEventListener('click', () => {
|
this._assertTextToggle.addEventListener('click', () => {
|
||||||
|
|
@ -853,7 +767,7 @@ class Overlay {
|
||||||
|
|
||||||
install() {
|
install() {
|
||||||
this._recorder.highlight.appendChild(this._overlayElement);
|
this._recorder.highlight.appendChild(this._overlayElement);
|
||||||
this._measure = this._overlayElement.getBoundingClientRect();
|
this._updateVisualPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
contains(element: Element) {
|
contains(element: Element) {
|
||||||
|
|
@ -874,13 +788,31 @@ class Overlay {
|
||||||
this._updateVisualPosition();
|
this._updateVisualPosition();
|
||||||
}
|
}
|
||||||
if (state.mode === 'none')
|
if (state.mode === 'none')
|
||||||
this._overlayElement.setAttribute('hidden', 'true');
|
this._hideOverlay();
|
||||||
else
|
else
|
||||||
|
this._showOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
flashToolSucceeded(tool: 'assertingVisibility' | 'assertingValue') {
|
||||||
|
const element = tool === 'assertingVisibility' ? this._assertVisibilityToggle : this._assertValuesToggle;
|
||||||
|
element.classList.add('succeeded');
|
||||||
|
setTimeout(() => element.classList.remove('succeeded'), 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _hideOverlay() {
|
||||||
|
this._overlayElement.setAttribute('hidden', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
private _showOverlay() {
|
||||||
|
if (!this._overlayElement.hasAttribute('hidden'))
|
||||||
|
return;
|
||||||
this._overlayElement.removeAttribute('hidden');
|
this._overlayElement.removeAttribute('hidden');
|
||||||
|
this._updateVisualPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateVisualPosition() {
|
private _updateVisualPosition() {
|
||||||
this._overlayElement.style.left = (this._recorder.injectedScript.window.innerWidth / 2 + this._offsetX) + 'px';
|
this._measure = this._overlayElement.getBoundingClientRect();
|
||||||
|
this._overlayElement.style.left = ((this._recorder.injectedScript.window.innerWidth - this._measure.width) / 2 + this._offsetX) + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove(event: MouseEvent) {
|
onMouseMove(event: MouseEvent) {
|
||||||
|
|
@ -890,8 +822,8 @@ class Overlay {
|
||||||
}
|
}
|
||||||
if (this._dragState) {
|
if (this._dragState) {
|
||||||
this._offsetX = this._dragState.offsetX + event.clientX - this._dragState.dragStart.x;
|
this._offsetX = this._dragState.offsetX + event.clientX - this._dragState.dragStart.x;
|
||||||
this._offsetX = Math.min(this._recorder.injectedScript.window.innerWidth / 2 - 10 - this._measure.width, this._offsetX);
|
const halfGapSize = (this._recorder.injectedScript.window.innerWidth - this._measure.width) / 2 - 10;
|
||||||
this._offsetX = Math.max(10 - this._recorder.injectedScript.window.innerWidth / 2, this._offsetX);
|
this._offsetX = Math.max(-halfGapSize, Math.min(halfGapSize, this._offsetX));
|
||||||
this._updateVisualPosition();
|
this._updateVisualPosition();
|
||||||
this._recorder.delegate.setOverlayState?.({ offsetX: this._offsetX });
|
this._recorder.delegate.setOverlayState?.({ offsetX: this._offsetX });
|
||||||
consumeEvent(event);
|
consumeEvent(event);
|
||||||
|
|
@ -925,7 +857,7 @@ export class Recorder {
|
||||||
private _tools: Record<Mode, RecorderTool>;
|
private _tools: Record<Mode, RecorderTool>;
|
||||||
private _actionSelectorModel: HighlightModel | null = null;
|
private _actionSelectorModel: HighlightModel | null = null;
|
||||||
readonly highlight: Highlight;
|
readonly highlight: Highlight;
|
||||||
private _overlay: Overlay | undefined;
|
readonly overlay: Overlay | undefined;
|
||||||
private _styleElement: HTMLStyleElement;
|
private _styleElement: HTMLStyleElement;
|
||||||
state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { offsetX: 0 } };
|
state: UIState = { mode: 'none', testIdAttributeName: 'data-testid', language: 'javascript', overlay: { offsetX: 0 } };
|
||||||
readonly document: Document;
|
readonly document: Document;
|
||||||
|
|
@ -947,8 +879,8 @@ export class Recorder {
|
||||||
};
|
};
|
||||||
this._currentTool = this._tools.none;
|
this._currentTool = this._tools.none;
|
||||||
if (injectedScript.window.top === injectedScript.window) {
|
if (injectedScript.window.top === injectedScript.window) {
|
||||||
this._overlay = new Overlay(this);
|
this.overlay = new Overlay(this);
|
||||||
this._overlay.setUIState(this.state);
|
this.overlay.setUIState(this.state);
|
||||||
}
|
}
|
||||||
this._styleElement = this.document.createElement('style');
|
this._styleElement = this.document.createElement('style');
|
||||||
this._styleElement.textContent = `
|
this._styleElement.textContent = `
|
||||||
|
|
@ -976,11 +908,12 @@ export class Recorder {
|
||||||
addEventListener(this.document, 'mouseup', event => this._onMouseUp(event as MouseEvent), true),
|
addEventListener(this.document, 'mouseup', event => this._onMouseUp(event as MouseEvent), true),
|
||||||
addEventListener(this.document, 'mousemove', event => this._onMouseMove(event as MouseEvent), true),
|
addEventListener(this.document, 'mousemove', event => this._onMouseMove(event as MouseEvent), true),
|
||||||
addEventListener(this.document, 'mouseleave', event => this._onMouseLeave(event as MouseEvent), true),
|
addEventListener(this.document, 'mouseleave', event => this._onMouseLeave(event as MouseEvent), true),
|
||||||
|
addEventListener(this.document, 'mouseenter', event => this._onMouseEnter(event as MouseEvent), true),
|
||||||
addEventListener(this.document, 'focus', event => this._onFocus(event), true),
|
addEventListener(this.document, 'focus', event => this._onFocus(event), true),
|
||||||
addEventListener(this.document, 'scroll', event => this._onScroll(event), true),
|
addEventListener(this.document, 'scroll', event => this._onScroll(event), true),
|
||||||
];
|
];
|
||||||
this.highlight.install();
|
this.highlight.install();
|
||||||
this._overlay?.install();
|
this.overlay?.install();
|
||||||
this.injectedScript.document.head.appendChild(this._styleElement);
|
this.injectedScript.document.head.appendChild(this._styleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1011,7 +944,7 @@ export class Recorder {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.highlight.setLanguage(state.language);
|
this.highlight.setLanguage(state.language);
|
||||||
this._switchCurrentTool();
|
this._switchCurrentTool();
|
||||||
this._overlay?.setUIState(state);
|
this.overlay?.setUIState(state);
|
||||||
|
|
||||||
// Race or scroll.
|
// Race or scroll.
|
||||||
if (this._actionSelectorModel?.selector && !this._actionSelectorModel?.elements.length)
|
if (this._actionSelectorModel?.selector && !this._actionSelectorModel?.elements.length)
|
||||||
|
|
@ -1030,7 +963,7 @@ export class Recorder {
|
||||||
private _onClick(event: MouseEvent) {
|
private _onClick(event: MouseEvent) {
|
||||||
if (!event.isTrusted)
|
if (!event.isTrusted)
|
||||||
return;
|
return;
|
||||||
if (this._overlay?.onClick(event))
|
if (this.overlay?.onClick(event))
|
||||||
return;
|
return;
|
||||||
if (this._ignoreOverlayEvent(event))
|
if (this._ignoreOverlayEvent(event))
|
||||||
return;
|
return;
|
||||||
|
|
@ -1072,7 +1005,7 @@ export class Recorder {
|
||||||
private _onMouseUp(event: MouseEvent) {
|
private _onMouseUp(event: MouseEvent) {
|
||||||
if (!event.isTrusted)
|
if (!event.isTrusted)
|
||||||
return;
|
return;
|
||||||
if (this._overlay?.onMouseUp(event))
|
if (this.overlay?.onMouseUp(event))
|
||||||
return;
|
return;
|
||||||
if (this._ignoreOverlayEvent(event))
|
if (this._ignoreOverlayEvent(event))
|
||||||
return;
|
return;
|
||||||
|
|
@ -1082,13 +1015,21 @@ export class Recorder {
|
||||||
private _onMouseMove(event: MouseEvent) {
|
private _onMouseMove(event: MouseEvent) {
|
||||||
if (!event.isTrusted)
|
if (!event.isTrusted)
|
||||||
return;
|
return;
|
||||||
if (this._overlay?.onMouseMove(event))
|
if (this.overlay?.onMouseMove(event))
|
||||||
return;
|
return;
|
||||||
if (this._ignoreOverlayEvent(event))
|
if (this._ignoreOverlayEvent(event))
|
||||||
return;
|
return;
|
||||||
this._currentTool.onMouseMove?.(event);
|
this._currentTool.onMouseMove?.(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _onMouseEnter(event: MouseEvent) {
|
||||||
|
if (!event.isTrusted)
|
||||||
|
return;
|
||||||
|
if (this._ignoreOverlayEvent(event))
|
||||||
|
return;
|
||||||
|
this._currentTool.onMouseEnter?.(event);
|
||||||
|
}
|
||||||
|
|
||||||
private _onMouseLeave(event: MouseEvent) {
|
private _onMouseLeave(event: MouseEvent) {
|
||||||
if (!event.isTrusted)
|
if (!event.isTrusted)
|
||||||
return;
|
return;
|
||||||
|
|
@ -1149,7 +1090,7 @@ export class Recorder {
|
||||||
|
|
||||||
deepEventTarget(event: Event): HTMLElement {
|
deepEventTarget(event: Event): HTMLElement {
|
||||||
for (const element of event.composedPath()) {
|
for (const element of event.composedPath()) {
|
||||||
if (!this._overlay?.contains(element as Element))
|
if (!this.overlay?.contains(element as Element))
|
||||||
return element as HTMLElement;
|
return element as HTMLElement;
|
||||||
}
|
}
|
||||||
return event.composedPath()[0] as HTMLElement;
|
return event.composedPath()[0] as HTMLElement;
|
||||||
|
|
@ -1301,14 +1242,4 @@ export class PollingRecorder implements RecorderDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmModeForLanguage(language: Language): string {
|
|
||||||
if (language === 'python')
|
|
||||||
return 'python';
|
|
||||||
if (language === 'java')
|
|
||||||
return 'text/x-java';
|
|
||||||
if (language === 'csharp')
|
|
||||||
return 'text/x-csharp';
|
|
||||||
return 'javascript';
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PollingRecorder;
|
export default PollingRecorder;
|
||||||
|
|
|
||||||
|
|
@ -373,6 +373,8 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
}
|
}
|
||||||
|
|
||||||
onCallLog(sdkObject: SdkObject, metadata: CallMetadata, logName: string, message: string) {
|
onCallLog(sdkObject: SdkObject, metadata: CallMetadata, logName: string, message: string) {
|
||||||
|
if (metadata.isServerSide || metadata.internal)
|
||||||
|
return;
|
||||||
if (logName !== 'api')
|
if (logName !== 'api')
|
||||||
return;
|
return;
|
||||||
const event = createActionLogTraceEvent(metadata, message);
|
const event = createActionLogTraceEvent(metadata, message);
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ export async function mkdirIfNeeded(filePath: string) {
|
||||||
|
|
||||||
export async function removeFolders(dirs: string[]): Promise<Error[]> {
|
export async function removeFolders(dirs: string[]): Promise<Error[]> {
|
||||||
return await Promise.all(dirs.map((dir: string) =>
|
return await Promise.all(dirs.map((dir: string) =>
|
||||||
fs.promises.rm(dir, { recursive: true, force: true, maxRetries: 10 })
|
fs.promises.rm(dir, { recursive: true, force: true, maxRetries: 10 }).catch(e => e)
|
||||||
)).catch(e => e);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canAccessFile(file: string) {
|
export function canAccessFile(file: string) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-core",
|
"name": "@playwright/experimental-ct-core",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "Playwright Component Testing Helpers",
|
"description": "Playwright Component Testing Helpers",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -27,9 +27,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next",
|
"playwright-core": "1.40.1",
|
||||||
"vite": "^4.4.10",
|
"vite": "^4.4.10",
|
||||||
"playwright": "1.40.0-next"
|
"playwright": "1.40.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-react",
|
"name": "@playwright/experimental-ct-react",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "Playwright Component Testing for React",
|
"description": "Playwright Component Testing for React",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@vitejs/plugin-react": "^4.0.0"
|
"@vitejs/plugin-react": "^4.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-react17",
|
"name": "@playwright/experimental-ct-react17",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "Playwright Component Testing for React",
|
"description": "Playwright Component Testing for React",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@vitejs/plugin-react": "^4.0.0"
|
"@vitejs/plugin-react": "^4.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-solid",
|
"name": "@playwright/experimental-ct-solid",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "Playwright Component Testing for Solid",
|
"description": "Playwright Component Testing for Solid",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"vite-plugin-solid": "^2.7.0"
|
"vite-plugin-solid": "^2.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-svelte",
|
"name": "@playwright/experimental-ct-svelte",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "Playwright Component Testing for Svelte",
|
"description": "Playwright Component Testing for Svelte",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.1.1"
|
"@sveltejs/vite-plugin-svelte": "^2.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-vue",
|
"name": "@playwright/experimental-ct-vue",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "Playwright Component Testing for Vue",
|
"description": "Playwright Component Testing for Vue",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@vitejs/plugin-vue": "^4.2.1"
|
"@vitejs/plugin-vue": "^4.2.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-vue2",
|
"name": "@playwright/experimental-ct-vue2",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "Playwright Component Testing for Vue2",
|
"description": "Playwright Component Testing for Vue2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.40.0-next",
|
"@playwright/experimental-ct-core": "1.40.1",
|
||||||
"@vitejs/plugin-vue2": "^2.2.0"
|
"@vitejs/plugin-vue2": "^2.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "playwright-firefox",
|
"name": "playwright-firefox",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "A high-level API to automate Firefox",
|
"description": "A high-level API to automate Firefox",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -30,6 +30,6 @@
|
||||||
"install": "node install.js"
|
"install": "node install.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/test",
|
"name": "@playwright/test",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "A high-level API to automate web browsers",
|
"description": "A high-level API to automate web browsers",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -30,6 +30,6 @@
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.40.0-next"
|
"playwright": "1.40.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "playwright-webkit",
|
"name": "playwright-webkit",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "A high-level API to automate WebKit",
|
"description": "A high-level API to automate WebKit",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -30,6 +30,6 @@
|
||||||
"install": "node install.js"
|
"install": "node install.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "playwright",
|
"name": "playwright",
|
||||||
"version": "1.40.0-next",
|
"version": "1.40.1",
|
||||||
"description": "A high-level API to automate web browsers",
|
"description": "A high-level API to automate web browsers",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.40.0-next"
|
"playwright-core": "1.40.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"fsevents": "2.3.2"
|
"fsevents": "2.3.2"
|
||||||
|
|
|
||||||
|
|
@ -239,9 +239,6 @@ export class TestCase extends Base implements reporterTypes.TestCase {
|
||||||
_poolDigest = '';
|
_poolDigest = '';
|
||||||
_workerHash = '';
|
_workerHash = '';
|
||||||
_projectId = '';
|
_projectId = '';
|
||||||
// This is different from |results.length| because sometimes we do not run the test, but consume
|
|
||||||
// an attempt, for example when skipping tests in a serial suite after a failure.
|
|
||||||
_runAttempts = 0;
|
|
||||||
// Annotations known statically before running the test, e.g. `test.skip()` or `test.describe.skip()`.
|
// Annotations known statically before running the test, e.g. `test.skip()` or `test.describe.skip()`.
|
||||||
_staticAnnotations: Annotation[] = [];
|
_staticAnnotations: Annotation[] = [];
|
||||||
|
|
||||||
|
|
@ -259,10 +256,16 @@ export class TestCase extends Base implements reporterTypes.TestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
outcome(): 'skipped' | 'expected' | 'unexpected' | 'flaky' {
|
outcome(): 'skipped' | 'expected' | 'unexpected' | 'flaky' {
|
||||||
const results = this.results.filter(result => result.status !== 'interrupted');
|
// Ignore initial skips that may be a result of "skipped because previous test in serial mode failed".
|
||||||
if (results.every(result => result.status === 'skipped'))
|
const results = [...this.results];
|
||||||
|
while (results[0]?.status === 'skipped' || results[0]?.status === 'interrupted')
|
||||||
|
results.shift();
|
||||||
|
|
||||||
|
// All runs were skipped.
|
||||||
|
if (!results.length)
|
||||||
return 'skipped';
|
return 'skipped';
|
||||||
const failures = results.filter(result => result.status !== this.expectedStatus);
|
|
||||||
|
const failures = results.filter(result => result.status !== 'skipped' && result.status !== 'interrupted' && result.status !== this.expectedStatus);
|
||||||
if (!failures.length) // all passed
|
if (!failures.length) // all passed
|
||||||
return 'expected';
|
return 'expected';
|
||||||
if (failures.length === results.length) // all failed
|
if (failures.length === results.length) // all failed
|
||||||
|
|
|
||||||
|
|
@ -492,10 +492,16 @@ export class TeleTestCase implements reporterTypes.TestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
outcome(): 'skipped' | 'expected' | 'unexpected' | 'flaky' {
|
outcome(): 'skipped' | 'expected' | 'unexpected' | 'flaky' {
|
||||||
const results = this.results.filter(result => result.status !== 'interrupted');
|
// Ignore initial skips that may be a result of "skipped because previous test in serial mode failed".
|
||||||
if (results.every(result => result.status === 'skipped'))
|
const results = [...this.results];
|
||||||
|
while (results[0]?.status === 'skipped' || results[0]?.status === 'interrupted')
|
||||||
|
results.shift();
|
||||||
|
|
||||||
|
// All runs were skipped.
|
||||||
|
if (!results.length)
|
||||||
return 'skipped';
|
return 'skipped';
|
||||||
const failures = results.filter(result => result.status !== this.expectedStatus);
|
|
||||||
|
const failures = results.filter(result => result.status !== 'skipped' && result.status !== 'interrupted' && result.status !== this.expectedStatus);
|
||||||
if (!failures.length) // all passed
|
if (!failures.length) // all passed
|
||||||
return 'expected';
|
return 'expected';
|
||||||
if (failures.length === results.length) // all failed
|
if (failures.length === results.length) // all failed
|
||||||
|
|
|
||||||
|
|
@ -92,19 +92,37 @@ const commonEvents = new Set(commonEventNames);
|
||||||
const commonEventRegex = new RegExp(`${commonEventNames.join('|')}`);
|
const commonEventRegex = new RegExp(`${commonEventNames.join('|')}`);
|
||||||
|
|
||||||
function parseCommonEvents(reportJsonl: Buffer): JsonEvent[] {
|
function parseCommonEvents(reportJsonl: Buffer): JsonEvent[] {
|
||||||
return reportJsonl.toString().split('\n')
|
return splitBufferLines(reportJsonl)
|
||||||
|
.map(line => line.toString('utf8'))
|
||||||
.filter(line => commonEventRegex.test(line)) // quick filter
|
.filter(line => commonEventRegex.test(line)) // quick filter
|
||||||
.map(line => JSON.parse(line) as JsonEvent)
|
.map(line => JSON.parse(line) as JsonEvent)
|
||||||
.filter(event => commonEvents.has(event.method));
|
.filter(event => commonEvents.has(event.method));
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTestEvents(reportJsonl: Buffer): JsonEvent[] {
|
function parseTestEvents(reportJsonl: Buffer): JsonEvent[] {
|
||||||
return reportJsonl.toString().split('\n')
|
return splitBufferLines(reportJsonl)
|
||||||
|
.map(line => line.toString('utf8'))
|
||||||
.filter(line => line.length)
|
.filter(line => line.length)
|
||||||
.map(line => JSON.parse(line) as JsonEvent)
|
.map(line => JSON.parse(line) as JsonEvent)
|
||||||
.filter(event => !commonEvents.has(event.method));
|
.filter(event => !commonEvents.has(event.method));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function splitBufferLines(buffer: Buffer) {
|
||||||
|
const lines = [];
|
||||||
|
let start = 0;
|
||||||
|
while (start < buffer.length) {
|
||||||
|
// 0x0A is the byte for '\n'
|
||||||
|
const end = buffer.indexOf(0x0A, start);
|
||||||
|
if (end === -1) {
|
||||||
|
lines.push(buffer.slice(start));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lines.push(buffer.slice(start, end));
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
async function extractAndParseReports(dir: string, shardFiles: string[], internalizer: JsonStringInternalizer, printStatus: StatusCallback) {
|
async function extractAndParseReports(dir: string, shardFiles: string[], internalizer: JsonStringInternalizer, printStatus: StatusCallback) {
|
||||||
const shardEvents: { file: string, localPath: string, metadata: BlobReportMetadata, parsedEvents: JsonEvent[] }[] = [];
|
const shardEvents: { file: string, localPath: string, metadata: BlobReportMetadata, parsedEvents: JsonEvent[] }[] = [];
|
||||||
await fs.promises.mkdir(path.join(dir, 'resources'), { recursive: true });
|
await fs.promises.mkdir(path.join(dir, 'resources'), { recursive: true });
|
||||||
|
|
|
||||||
|
|
@ -290,7 +290,7 @@ class JobDispatcher {
|
||||||
test.expectedStatus = params.expectedStatus;
|
test.expectedStatus = params.expectedStatus;
|
||||||
test.annotations = params.annotations;
|
test.annotations = params.annotations;
|
||||||
test.timeout = params.timeout;
|
test.timeout = params.timeout;
|
||||||
const isFailure = result.status !== test.expectedStatus;
|
const isFailure = result.status !== 'skipped' && result.status !== test.expectedStatus;
|
||||||
if (isFailure)
|
if (isFailure)
|
||||||
this._failedTests.add(test);
|
this._failedTests.add(test);
|
||||||
this._reportTestEnd(test, result);
|
this._reportTestEnd(test, result);
|
||||||
|
|
@ -369,17 +369,22 @@ class JobDispatcher {
|
||||||
}
|
}
|
||||||
result.errors = [...errors];
|
result.errors = [...errors];
|
||||||
result.error = result.errors[0];
|
result.error = result.errors[0];
|
||||||
result.status = 'failed';
|
result.status = errors.length ? 'failed' : 'skipped';
|
||||||
this._reportTestEnd(test, result);
|
this._reportTestEnd(test, result);
|
||||||
this._failedTests.add(test);
|
this._failedTests.add(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleFatalErrors(errors: TestError[]) {
|
private _massSkipTestsFromRemaining(testIds: Set<string>, errors: TestError[]) {
|
||||||
const test = this._remainingByTestId.values().next().value as TestCase | undefined;
|
for (const test of this._remainingByTestId.values()) {
|
||||||
if (test) {
|
if (!testIds.has(test.id))
|
||||||
|
continue;
|
||||||
|
if (!this._failureTracker.hasReachedMaxFailures()) {
|
||||||
this._failTestWithErrors(test, errors);
|
this._failTestWithErrors(test, errors);
|
||||||
|
errors = []; // Only report errors for the first test.
|
||||||
|
}
|
||||||
this._remainingByTestId.delete(test.id);
|
this._remainingByTestId.delete(test.id);
|
||||||
} else if (errors.length) {
|
}
|
||||||
|
if (errors.length) {
|
||||||
// We had fatal errors after all tests have passed - most likely in some teardown.
|
// We had fatal errors after all tests have passed - most likely in some teardown.
|
||||||
// Let's just fail the test run.
|
// Let's just fail the test run.
|
||||||
this._failureTracker.onWorkerError();
|
this._failureTracker.onWorkerError();
|
||||||
|
|
@ -407,28 +412,23 @@ class JobDispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.fatalErrors.length) {
|
if (params.fatalErrors.length) {
|
||||||
// In case of fatal errors, report the first remaining test as failing with these errors,
|
// In case of fatal errors, report first remaining test as failing with these errors,
|
||||||
// and "skip" all other tests to avoid running into the same issue over and over.
|
// and all others as skipped.
|
||||||
this._handleFatalErrors(params.fatalErrors);
|
this._massSkipTestsFromRemaining(new Set(this._remainingByTestId.keys()), params.fatalErrors);
|
||||||
this._remainingByTestId.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle tests that should be skipped because of the setup failure.
|
// Handle tests that should be skipped because of the setup failure.
|
||||||
for (const testId of params.skipTestsDueToSetupFailure)
|
this._massSkipTestsFromRemaining(new Set(params.skipTestsDueToSetupFailure), []);
|
||||||
this._remainingByTestId.delete(testId);
|
|
||||||
|
|
||||||
if (params.unexpectedExitError) {
|
if (params.unexpectedExitError) {
|
||||||
if (this._currentlyRunning) {
|
|
||||||
// When worker exits during a test, we blame the test itself.
|
// When worker exits during a test, we blame the test itself.
|
||||||
this._failTestWithErrors(this._currentlyRunning.test, [params.unexpectedExitError]);
|
//
|
||||||
this._remainingByTestId.delete(this._currentlyRunning.test.id);
|
|
||||||
} else {
|
|
||||||
// The most common situation when worker exits while not running a test is:
|
// The most common situation when worker exits while not running a test is:
|
||||||
// worker failed to require the test file (at the start) because of an exception in one of imports.
|
// worker failed to require the test file (at the start) because of an exception in one of imports.
|
||||||
// In this case, "skip" all remaining tests, to avoid running into the same exception over and over.
|
// In this case, "skip" all remaining tests, to avoid running into the same exception over and over.
|
||||||
this._handleFatalErrors([params.unexpectedExitError]);
|
if (this._currentlyRunning)
|
||||||
this._remainingByTestId.clear();
|
this._massSkipTestsFromRemaining(new Set([this._currentlyRunning.test.id]), [params.unexpectedExitError]);
|
||||||
}
|
else
|
||||||
|
this._massSkipTestsFromRemaining(new Set(this._remainingByTestId.keys()), [params.unexpectedExitError]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const retryCandidates = new Set<TestCase>();
|
const retryCandidates = new Set<TestCase>();
|
||||||
|
|
@ -446,22 +446,26 @@ class JobDispatcher {
|
||||||
serialSuitesWithFailures.add(outermostSerialSuite);
|
serialSuitesWithFailures.add(outermostSerialSuite);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const serialSuite of serialSuitesWithFailures) {
|
// If we have failed tests that belong to a serial suite,
|
||||||
serialSuite.allTests().forEach(test => {
|
// we should skip all future tests from the same serial suite.
|
||||||
// Skip remaining tests from serial suites with failures.
|
const testsBelongingToSomeSerialSuiteWithFailures = [...this._remainingByTestId.values()].filter(test => {
|
||||||
this._remainingByTestId.delete(test.id);
|
let parent: Suite | undefined = test.parent;
|
||||||
// Schedule them for the retry all together.
|
while (parent && !serialSuitesWithFailures.has(parent))
|
||||||
retryCandidates.add(test);
|
parent = parent.parent;
|
||||||
|
return !!parent;
|
||||||
});
|
});
|
||||||
|
this._massSkipTestsFromRemaining(new Set(testsBelongingToSomeSerialSuiteWithFailures.map(test => test.id)), []);
|
||||||
|
|
||||||
|
for (const serialSuite of serialSuitesWithFailures) {
|
||||||
|
// Add all tests from failed serial suites for possible retry.
|
||||||
|
// These will only be retried together, because they have the same
|
||||||
|
// "retries" setting and the same number of previous runs.
|
||||||
|
serialSuite.allTests().forEach(test => retryCandidates.add(test));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const test of this._job.tests) {
|
|
||||||
if (!this._remainingByTestId.has(test.id))
|
|
||||||
++test._runAttempts;
|
|
||||||
}
|
|
||||||
const remaining = [...this._remainingByTestId.values()];
|
const remaining = [...this._remainingByTestId.values()];
|
||||||
for (const test of retryCandidates) {
|
for (const test of retryCandidates) {
|
||||||
if (test._runAttempts < test.retries + 1)
|
if (test.results.length < test.retries + 1)
|
||||||
remaining.push(test);
|
remaining.push(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export class FailureTracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
onTestEnd(test: TestCase, result: TestResult) {
|
onTestEnd(test: TestCase, result: TestResult) {
|
||||||
if (result.status !== test.expectedStatus)
|
if (result.status !== 'skipped' && result.status !== test.expectedStatus)
|
||||||
++this._failureCount;
|
++this._failureCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,10 @@ export class TraceModel {
|
||||||
}
|
}
|
||||||
case 'log': {
|
case 'log': {
|
||||||
const existing = actionMap.get(event.callId);
|
const existing = actionMap.get(event.callId);
|
||||||
existing!.log.push({
|
// We have some corrupted traces out there, tolerate them.
|
||||||
|
if (!existing)
|
||||||
|
return;
|
||||||
|
existing.log.push({
|
||||||
time: event.time,
|
time: event.time,
|
||||||
message: event.message,
|
message: event.message,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -399,7 +399,7 @@ test('beforeAll failure should prevent the test, but not afterAll', async ({ run
|
||||||
});
|
});
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(result.didNotRun).toBe(1);
|
expect(result.skipped).toBe(1);
|
||||||
expect(result.outputLines).toEqual([
|
expect(result.outputLines).toEqual([
|
||||||
'beforeAll',
|
'beforeAll',
|
||||||
'afterAll',
|
'afterAll',
|
||||||
|
|
@ -499,7 +499,7 @@ test('beforeAll timeout should be reported and prevent more tests', async ({ run
|
||||||
}, { timeout: 1000 });
|
}, { timeout: 1000 });
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(result.didNotRun).toBe(1);
|
expect(result.skipped).toBe(1);
|
||||||
expect(result.outputLines).toEqual([
|
expect(result.outputLines).toEqual([
|
||||||
'beforeAll',
|
'beforeAll',
|
||||||
'afterAll',
|
'afterAll',
|
||||||
|
|
@ -688,7 +688,7 @@ test('unhandled rejection during beforeAll should be reported and prevent more t
|
||||||
});
|
});
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(result.didNotRun).toBe(1);
|
expect(result.skipped).toBe(1);
|
||||||
expect(result.outputLines).toEqual([
|
expect(result.outputLines).toEqual([
|
||||||
'beforeAll',
|
'beforeAll',
|
||||||
'afterAll',
|
'afterAll',
|
||||||
|
|
@ -801,7 +801,7 @@ test('beforeAll failure should only prevent tests that are affected', async ({ r
|
||||||
});
|
});
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(result.didNotRun).toBe(1);
|
expect(result.skipped).toBe(1);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
expect(result.outputLines).toEqual([
|
expect(result.outputLines).toEqual([
|
||||||
'beforeAll',
|
'beforeAll',
|
||||||
|
|
|
||||||
|
|
@ -216,8 +216,8 @@ test('should retry beforeAll failure', async ({ runInlineTest }) => {
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.passed).toBe(0);
|
expect(result.passed).toBe(0);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(result.didNotRun).toBe(1);
|
expect(result.skipped).toBe(1);
|
||||||
expect(result.output.split('\n')[2]).toBe('××F');
|
expect(result.output.split('\n')[2]).toBe('×°×°F°');
|
||||||
expect(result.output).toContain('BeforeAll is bugged!');
|
expect(result.output).toContain('BeforeAll is bugged!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ test('should report subprocess creation error', async ({ runInlineTest }, testIn
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.passed).toBe(0);
|
expect(result.passed).toBe(0);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(result.didNotRun).toBe(1);
|
expect(result.skipped).toBe(1);
|
||||||
expect(result.output).toContain('Error: worker process exited unexpectedly (code=42, signal=null)');
|
expect(result.output).toContain('Error: worker process exited unexpectedly (code=42, signal=null)');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -634,9 +634,9 @@ test('should not hang on worker error in test file', async ({ runInlineTest }) =
|
||||||
`,
|
`,
|
||||||
}, { 'timeout': 3000 });
|
}, { 'timeout': 3000 });
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.results).toHaveLength(1);
|
|
||||||
expect(result.results[0].status).toBe('failed');
|
expect(result.results[0].status).toBe('failed');
|
||||||
expect(result.results[0].error.message).toContain('Error: worker process exited unexpectedly');
|
expect(result.results[0].error.message).toContain('Error: worker process exited unexpectedly');
|
||||||
|
expect(result.results[1].status).toBe('skipped');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fast double SIGINT should be ignored', async ({ interactWithTestRunner }) => {
|
test('fast double SIGINT should be ignored', async ({ interactWithTestRunner }) => {
|
||||||
|
|
|
||||||
|
|
@ -640,7 +640,7 @@ test('static modifiers should be added in serial mode', async ({ runInlineTest }
|
||||||
});
|
});
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.passed).toBe(0);
|
expect(result.passed).toBe(0);
|
||||||
expect(result.didNotRun).toBe(3);
|
expect(result.skipped).toBe(3);
|
||||||
expect(result.report.suites[0].specs[0].tests[0].annotations).toEqual([{ type: 'slow' }]);
|
expect(result.report.suites[0].specs[0].tests[0].annotations).toEqual([{ type: 'slow' }]);
|
||||||
expect(result.report.suites[0].specs[1].tests[0].annotations).toEqual([{ type: 'fixme' }]);
|
expect(result.report.suites[0].specs[1].tests[0].annotations).toEqual([{ type: 'fixme' }]);
|
||||||
expect(result.report.suites[0].specs[2].tests[0].annotations).toEqual([{ type: 'skip' }]);
|
expect(result.report.suites[0].specs[2].tests[0].annotations).toEqual([{ type: 'skip' }]);
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ test('test.describe.serial should work', async ({ runInlineTest }) => {
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.passed).toBe(2);
|
expect(result.passed).toBe(2);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(result.didNotRun).toBe(2);
|
expect(result.skipped).toBe(2);
|
||||||
expect(result.outputLines).toEqual([
|
expect(result.outputLines).toEqual([
|
||||||
'test1',
|
'test1',
|
||||||
'test2',
|
'test2',
|
||||||
|
|
@ -87,7 +87,7 @@ test('test.describe.serial should work in describe', async ({ runInlineTest }) =
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.passed).toBe(2);
|
expect(result.passed).toBe(2);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(result.didNotRun).toBe(2);
|
expect(result.skipped).toBe(2);
|
||||||
expect(result.outputLines).toEqual([
|
expect(result.outputLines).toEqual([
|
||||||
'test1',
|
'test1',
|
||||||
'test2',
|
'test2',
|
||||||
|
|
@ -128,7 +128,7 @@ test('test.describe.serial should work with retry', async ({ runInlineTest }) =>
|
||||||
expect(result.passed).toBe(2);
|
expect(result.passed).toBe(2);
|
||||||
expect(result.flaky).toBe(1);
|
expect(result.flaky).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(result.didNotRun).toBe(1);
|
expect(result.skipped).toBe(1);
|
||||||
expect(result.outputLines).toEqual([
|
expect(result.outputLines).toEqual([
|
||||||
'test1',
|
'test1',
|
||||||
'test2',
|
'test2',
|
||||||
|
|
@ -272,7 +272,7 @@ test('test.describe.serial should work with test.fail', async ({ runInlineTest }
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.passed).toBe(2);
|
expect(result.passed).toBe(2);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(result.didNotRun).toBe(1);
|
expect(result.skipped).toBe(1);
|
||||||
expect(result.outputLines).toEqual([
|
expect(result.outputLines).toEqual([
|
||||||
'zero',
|
'zero',
|
||||||
'one',
|
'one',
|
||||||
|
|
@ -394,55 +394,3 @@ test('test.describe.serial should work with fullyParallel', async ({ runInlineTe
|
||||||
'two',
|
'two',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('serial fail + skip is failed', async ({ runInlineTest }) => {
|
|
||||||
const result = await runInlineTest({
|
|
||||||
'a.test.ts': `
|
|
||||||
import { test, expect } from '@playwright/test';
|
|
||||||
test.describe.configure({ mode: 'serial', retries: 1 });
|
|
||||||
test.describe.serial('serial suite', () => {
|
|
||||||
test('one', async () => {
|
|
||||||
expect(test.info().retry).toBe(0);
|
|
||||||
});
|
|
||||||
test('two', async () => {
|
|
||||||
expect(1).toBe(2);
|
|
||||||
});
|
|
||||||
test('three', async () => {
|
|
||||||
});
|
|
||||||
});
|
|
||||||
`,
|
|
||||||
}, { workers: 1 });
|
|
||||||
expect(result.exitCode).toBe(1);
|
|
||||||
expect(result.passed).toBe(0);
|
|
||||||
expect(result.skipped).toBe(0);
|
|
||||||
expect(result.flaky).toBe(1);
|
|
||||||
expect(result.failed).toBe(1);
|
|
||||||
expect(result.interrupted).toBe(0);
|
|
||||||
expect(result.didNotRun).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('serial skip + fail is failed', async ({ runInlineTest }) => {
|
|
||||||
const result = await runInlineTest({
|
|
||||||
'a.test.ts': `
|
|
||||||
import { test, expect } from '@playwright/test';
|
|
||||||
test.describe.configure({ mode: 'serial', retries: 1 });
|
|
||||||
test.describe.serial('serial suite', () => {
|
|
||||||
test('one', async () => {
|
|
||||||
expect(test.info().retry).toBe(1);
|
|
||||||
});
|
|
||||||
test('two', async () => {
|
|
||||||
expect(1).toBe(2);
|
|
||||||
});
|
|
||||||
test('three', async () => {
|
|
||||||
});
|
|
||||||
});
|
|
||||||
`,
|
|
||||||
}, { workers: 1 });
|
|
||||||
expect(result.exitCode).toBe(1);
|
|
||||||
expect(result.passed).toBe(0);
|
|
||||||
expect(result.skipped).toBe(0);
|
|
||||||
expect(result.flaky).toBe(1);
|
|
||||||
expect(result.failed).toBe(1);
|
|
||||||
expect(result.interrupted).toBe(0);
|
|
||||||
expect(result.didNotRun).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import { test, expect, retries } from './ui-mode-fixtures';
|
||||||
|
|
||||||
test.describe.configure({ mode: 'parallel', retries });
|
test.describe.configure({ mode: 'parallel', retries });
|
||||||
|
|
||||||
test('should merge trace events', async ({ runUITest, server }) => {
|
test('should merge trace events', async ({ runUITest }) => {
|
||||||
const { page } = await runUITest({
|
const { page } = await runUITest({
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
@ -99,7 +99,7 @@ test('should merge screenshot assertions', async ({ runUITest }, testInfo) => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should locate sync assertions in source', async ({ runUITest, server }) => {
|
test('should locate sync assertions in source', async ({ runUITest }) => {
|
||||||
const { page } = await runUITest({
|
const { page } = await runUITest({
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
@ -118,7 +118,7 @@ test('should locate sync assertions in source', async ({ runUITest, server }) =>
|
||||||
).toHaveText('4 expect(1).toBe(1);');
|
).toHaveText('4 expect(1).toBe(1);');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show snapshots for sync assertions', async ({ runUITest, server }) => {
|
test('should show snapshots for sync assertions', async ({ runUITest }) => {
|
||||||
const { page } = await runUITest({
|
const { page } = await runUITest({
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
@ -150,7 +150,7 @@ test('should show snapshots for sync assertions', async ({ runUITest, server })
|
||||||
).toHaveText('Submit');
|
).toHaveText('Submit');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show image diff', async ({ runUITest, server }) => {
|
test('should show image diff', async ({ runUITest }) => {
|
||||||
const { page } = await runUITest({
|
const { page } = await runUITest({
|
||||||
'playwright.config.js': `
|
'playwright.config.js': `
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
@ -175,7 +175,7 @@ test('should show image diff', async ({ runUITest, server }) => {
|
||||||
await expect(page.locator('.image-diff-view .image-wrapper img')).toBeVisible();
|
await expect(page.locator('.image-diff-view .image-wrapper img')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show screenshot', async ({ runUITest, server }) => {
|
test('should show screenshot', async ({ runUITest }) => {
|
||||||
const { page } = await runUITest({
|
const { page } = await runUITest({
|
||||||
'playwright.config.js': `
|
'playwright.config.js': `
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
@ -197,3 +197,32 @@ test('should show screenshot', async ({ runUITest, server }) => {
|
||||||
await expect(page.getByText('Screenshots', { exact: true })).toBeVisible();
|
await expect(page.getByText('Screenshots', { exact: true })).toBeVisible();
|
||||||
await expect(page.locator('.attachment-item img')).toHaveCount(1);
|
await expect(page.locator('.attachment-item img')).toHaveCount(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not fail on internal page logs', async ({ runUITest, server }) => {
|
||||||
|
const { page } = await runUITest({
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('pass', async ({ browser }, testInfo) => {
|
||||||
|
const context = await browser.newContext({ storageState: { cookies: [], origins: [] } });
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto("${server.EMPTY_PAGE}");
|
||||||
|
await page.context().storageState({ path: testInfo.outputPath('storage.json') });
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByText('pass').dblclick();
|
||||||
|
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
listItem,
|
||||||
|
'action list'
|
||||||
|
).toHaveText([
|
||||||
|
/Before Hooks[\d.]+m?s/,
|
||||||
|
/browser.newContext[\d.]+m?s/,
|
||||||
|
/browserContext.newPage[\d.]+m?s/,
|
||||||
|
/page.goto/,
|
||||||
|
/browserContext.storageState[\d.]+m?s/,
|
||||||
|
/After Hooks/,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue