Merge branch 'main' into sharding-algorithm
This commit is contained in:
commit
ae34689e94
|
|
@ -11,7 +11,7 @@ const context = await browser.newContext();
|
||||||
await context.tracing.start({ screenshots: true, snapshots: true });
|
await context.tracing.start({ screenshots: true, snapshots: true });
|
||||||
const page = await context.newPage();
|
const page = await context.newPage();
|
||||||
await page.goto('https://playwright.dev');
|
await page.goto('https://playwright.dev');
|
||||||
await context.tracing.stop({ path: 'trace.pwtrace.zip' });
|
await context.tracing.stop({ path: 'trace.zip' });
|
||||||
```
|
```
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|
@ -23,7 +23,7 @@ context.tracing().start(new Tracing.StartOptions()
|
||||||
Page page = context.newPage();
|
Page page = context.newPage();
|
||||||
page.navigate("https://playwright.dev");
|
page.navigate("https://playwright.dev");
|
||||||
context.tracing().stop(new Tracing.StopOptions()
|
context.tracing().stop(new Tracing.StopOptions()
|
||||||
.setPath(Paths.get("trace.pwtrace.zip")));
|
.setPath(Paths.get("trace.zip")));
|
||||||
```
|
```
|
||||||
|
|
||||||
```python async
|
```python async
|
||||||
|
|
@ -32,7 +32,7 @@ context = await browser.new_context()
|
||||||
await context.tracing.start(screenshots=True, snapshots=True)
|
await context.tracing.start(screenshots=True, snapshots=True)
|
||||||
page = await context.new_page()
|
page = await context.new_page()
|
||||||
await page.goto("https://playwright.dev")
|
await page.goto("https://playwright.dev")
|
||||||
await context.tracing.stop(path = "trace.pwtrace.zip")
|
await context.tracing.stop(path = "trace.zip")
|
||||||
```
|
```
|
||||||
|
|
||||||
```python sync
|
```python sync
|
||||||
|
|
@ -41,7 +41,7 @@ context = browser.new_context()
|
||||||
context.tracing.start(screenshots=True, snapshots=True)
|
context.tracing.start(screenshots=True, snapshots=True)
|
||||||
page = context.new_page()
|
page = context.new_page()
|
||||||
page.goto("https://playwright.dev")
|
page.goto("https://playwright.dev")
|
||||||
context.tracing.stop(path = "trace.pwtrace.zip")
|
context.tracing.stop(path = "trace.zip")
|
||||||
```
|
```
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
|
@ -57,7 +57,7 @@ var page = await context.NewPageAsync();
|
||||||
await page.GotoAsync("https://playwright.dev");
|
await page.GotoAsync("https://playwright.dev");
|
||||||
await context.Tracing.StopAsync(new()
|
await context.Tracing.StopAsync(new()
|
||||||
{
|
{
|
||||||
Path = "trace.pwtrace.zip"
|
Path = "trace.zip"
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ Start tracing.
|
||||||
await context.tracing.start({ screenshots: true, snapshots: true });
|
await context.tracing.start({ screenshots: true, snapshots: true });
|
||||||
const page = await context.newPage();
|
const page = await context.newPage();
|
||||||
await page.goto('https://playwright.dev');
|
await page.goto('https://playwright.dev');
|
||||||
await context.tracing.stop({ path: 'trace.pwtrace.zip' });
|
await context.tracing.stop({ path: 'trace.zip' });
|
||||||
```
|
```
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|
@ -82,21 +82,21 @@ context.tracing().start(new Tracing.StartOptions()
|
||||||
Page page = context.newPage();
|
Page page = context.newPage();
|
||||||
page.navigate("https://playwright.dev");
|
page.navigate("https://playwright.dev");
|
||||||
context.tracing().stop(new Tracing.StopOptions()
|
context.tracing().stop(new Tracing.StopOptions()
|
||||||
.setPath(Paths.get("trace.pwtrace.zip")));
|
.setPath(Paths.get("trace.zip")));
|
||||||
```
|
```
|
||||||
|
|
||||||
```python async
|
```python async
|
||||||
await context.tracing.start(screenshots=True, snapshots=True)
|
await context.tracing.start(screenshots=True, snapshots=True)
|
||||||
page = await context.new_page()
|
page = await context.new_page()
|
||||||
await page.goto("https://playwright.dev")
|
await page.goto("https://playwright.dev")
|
||||||
await context.tracing.stop(path = "trace.pwtrace.zip")
|
await context.tracing.stop(path = "trace.zip")
|
||||||
```
|
```
|
||||||
|
|
||||||
```python sync
|
```python sync
|
||||||
context.tracing.start(screenshots=True, snapshots=True)
|
context.tracing.start(screenshots=True, snapshots=True)
|
||||||
page = context.new_page()
|
page = context.new_page()
|
||||||
page.goto("https://playwright.dev")
|
page.goto("https://playwright.dev")
|
||||||
context.tracing.stop(path = "trace.pwtrace.zip")
|
context.tracing.stop(path = "trace.zip")
|
||||||
```
|
```
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
|
@ -112,7 +112,7 @@ var page = await context.NewPageAsync();
|
||||||
await page.GotoAsync("https://playwright.dev");
|
await page.GotoAsync("https://playwright.dev");
|
||||||
await context.Tracing.StopAsync(new()
|
await context.Tracing.StopAsync(new()
|
||||||
{
|
{
|
||||||
Path = "trace.pwtrace.zip"
|
Path = "trace.zip"
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -177,12 +177,12 @@ await page.goto('https://playwright.dev');
|
||||||
await context.tracing.startChunk();
|
await context.tracing.startChunk();
|
||||||
await page.getByText('Get Started').click();
|
await page.getByText('Get Started').click();
|
||||||
// Everything between startChunk and stopChunk will be recorded in the trace.
|
// Everything between startChunk and stopChunk will be recorded in the trace.
|
||||||
await context.tracing.stopChunk({ path: 'trace1.pwtrace.zip' });
|
await context.tracing.stopChunk({ path: 'trace1.zip' });
|
||||||
|
|
||||||
await context.tracing.startChunk();
|
await context.tracing.startChunk();
|
||||||
await page.goto('http://example.com');
|
await page.goto('http://example.com');
|
||||||
// Save a second trace file with different actions.
|
// Save a second trace file with different actions.
|
||||||
await context.tracing.stopChunk({ path: 'trace2.pwtrace.zip' });
|
await context.tracing.stopChunk({ path: 'trace2.zip' });
|
||||||
```
|
```
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|
@ -196,13 +196,13 @@ context.tracing().startChunk();
|
||||||
page.getByText("Get Started").click();
|
page.getByText("Get Started").click();
|
||||||
// Everything between startChunk and stopChunk will be recorded in the trace.
|
// Everything between startChunk and stopChunk will be recorded in the trace.
|
||||||
context.tracing().stopChunk(new Tracing.StopChunkOptions()
|
context.tracing().stopChunk(new Tracing.StopChunkOptions()
|
||||||
.setPath(Paths.get("trace1.pwtrace.zip")));
|
.setPath(Paths.get("trace1.zip")));
|
||||||
|
|
||||||
context.tracing().startChunk();
|
context.tracing().startChunk();
|
||||||
page.navigate("http://example.com");
|
page.navigate("http://example.com");
|
||||||
// Save a second trace file with different actions.
|
// Save a second trace file with different actions.
|
||||||
context.tracing().stopChunk(new Tracing.StopChunkOptions()
|
context.tracing().stopChunk(new Tracing.StopChunkOptions()
|
||||||
.setPath(Paths.get("trace2.pwtrace.zip")));
|
.setPath(Paths.get("trace2.zip")));
|
||||||
```
|
```
|
||||||
|
|
||||||
```python async
|
```python async
|
||||||
|
|
@ -213,12 +213,12 @@ await page.goto("https://playwright.dev")
|
||||||
await context.tracing.start_chunk()
|
await context.tracing.start_chunk()
|
||||||
await page.get_by_text("Get Started").click()
|
await page.get_by_text("Get Started").click()
|
||||||
# Everything between start_chunk and stop_chunk will be recorded in the trace.
|
# Everything between start_chunk and stop_chunk will be recorded in the trace.
|
||||||
await context.tracing.stop_chunk(path = "trace1.pwtrace.zip")
|
await context.tracing.stop_chunk(path = "trace1.zip")
|
||||||
|
|
||||||
await context.tracing.start_chunk()
|
await context.tracing.start_chunk()
|
||||||
await page.goto("http://example.com")
|
await page.goto("http://example.com")
|
||||||
# Save a second trace file with different actions.
|
# Save a second trace file with different actions.
|
||||||
await context.tracing.stop_chunk(path = "trace2.pwtrace.zip")
|
await context.tracing.stop_chunk(path = "trace2.zip")
|
||||||
```
|
```
|
||||||
|
|
||||||
```python sync
|
```python sync
|
||||||
|
|
@ -229,12 +229,12 @@ page.goto("https://playwright.dev")
|
||||||
context.tracing.start_chunk()
|
context.tracing.start_chunk()
|
||||||
page.get_by_text("Get Started").click()
|
page.get_by_text("Get Started").click()
|
||||||
# Everything between start_chunk and stop_chunk will be recorded in the trace.
|
# Everything between start_chunk and stop_chunk will be recorded in the trace.
|
||||||
context.tracing.stop_chunk(path = "trace1.pwtrace.zip")
|
context.tracing.stop_chunk(path = "trace1.zip")
|
||||||
|
|
||||||
context.tracing.start_chunk()
|
context.tracing.start_chunk()
|
||||||
page.goto("http://example.com")
|
page.goto("http://example.com")
|
||||||
# Save a second trace file with different actions.
|
# Save a second trace file with different actions.
|
||||||
context.tracing.stop_chunk(path = "trace2.pwtrace.zip")
|
context.tracing.stop_chunk(path = "trace2.zip")
|
||||||
```
|
```
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
|
@ -254,7 +254,7 @@ await page.GetByText("Get Started").ClickAsync();
|
||||||
// Everything between StartChunkAsync and StopChunkAsync will be recorded in the trace.
|
// Everything between StartChunkAsync and StopChunkAsync will be recorded in the trace.
|
||||||
await context.Tracing.StopChunkAsync(new()
|
await context.Tracing.StopChunkAsync(new()
|
||||||
{
|
{
|
||||||
Path = "trace1.pwtrace.zip"
|
Path = "trace1.zip"
|
||||||
});
|
});
|
||||||
|
|
||||||
await context.Tracing.StartChunkAsync();
|
await context.Tracing.StartChunkAsync();
|
||||||
|
|
@ -262,7 +262,7 @@ await page.GotoAsync("http://example.com");
|
||||||
// Save a second trace file with different actions.
|
// Save a second trace file with different actions.
|
||||||
await context.Tracing.StopChunkAsync(new()
|
await context.Tracing.StopChunkAsync(new()
|
||||||
{
|
{
|
||||||
Path = "trace2.pwtrace.zip"
|
Path = "trace2.zip"
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -322,5 +322,5 @@ This step will not work for pull requests created from a forked repository becau
|
||||||
- [Learn how to perform Actions](./input.md)
|
- [Learn how to perform Actions](./input.md)
|
||||||
- [Learn how to write Assertions](./test-assertions.md)
|
- [Learn how to write Assertions](./test-assertions.md)
|
||||||
- [Learn more about the Trace Viewer](/trace-viewer.md)
|
- [Learn more about the Trace Viewer](/trace-viewer.md)
|
||||||
- [Learn more ways of running tests on GitHub Actions](/ci.md)
|
- [Learn more ways of running tests on GitHub Actions](/ci.md#github-actions)
|
||||||
- [Learn more about running tests on other CI providers](/ci.md#github-actions) // TODO: is this link correct?
|
- [Learn more about running tests on other CI providers](/ci.md)
|
||||||
|
|
|
||||||
|
|
@ -1710,6 +1710,11 @@ Step body.
|
||||||
|
|
||||||
Whether to box the step in the report. Defaults to `false`. When the step is boxed, errors thrown from the step internals point to the step call site. See below for more details.
|
Whether to box the step in the report. Defaults to `false`. When the step is boxed, errors thrown from the step internals point to the step call site. See below for more details.
|
||||||
|
|
||||||
|
### option: Test.step.location
|
||||||
|
* since: v1.48
|
||||||
|
- `location` <[Location]>
|
||||||
|
Specifies a custom location for the step to be shown in test reports. By default, location of the [`method: Test.step`] call is shown.
|
||||||
|
|
||||||
## method: Test.use
|
## method: Test.use
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -238,12 +238,12 @@ async function globalSetup(config: FullConfig) {
|
||||||
await page.getByText('Sign in').click();
|
await page.getByText('Sign in').click();
|
||||||
await context.storageState({ path: storageState as string });
|
await context.storageState({ path: storageState as string });
|
||||||
await context.tracing.stop({
|
await context.tracing.stop({
|
||||||
path: './test-results/setup-trace.pwtrace.zip',
|
path: './test-results/setup-trace.zip',
|
||||||
});
|
});
|
||||||
await browser.close();
|
await browser.close();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await context.tracing.stop({
|
await context.tracing.stop({
|
||||||
path: './test-results/failed-setup-trace.pwtrace.zip',
|
path: './test-results/failed-setup-trace.zip',
|
||||||
});
|
});
|
||||||
await browser.close();
|
await browser.close();
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ Options for tracing are:
|
||||||
- `off`: Do not record trace. (default)
|
- `off`: Do not record trace. (default)
|
||||||
- `retain-on-failure`: Record trace for each test, but remove all traces from successful test runs.
|
- `retain-on-failure`: Record trace for each test, but remove all traces from successful test runs.
|
||||||
|
|
||||||
This will record the trace and place it into the file named `trace.pwtrace.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>
|
<details>
|
||||||
<summary>If you are not using Pytest, click here to learn how to record traces.</summary>
|
<summary>If you are not using Pytest, click here to learn how to record traces.</summary>
|
||||||
|
|
@ -41,7 +41,7 @@ page = await context.new_page()
|
||||||
await page.goto("https://playwright.dev")
|
await page.goto("https://playwright.dev")
|
||||||
|
|
||||||
# Stop tracing and export it into a zip archive.
|
# Stop tracing and export it into a zip archive.
|
||||||
await context.tracing.stop(path = "trace.pwtrace.zip")
|
await context.tracing.stop(path = "trace.zip")
|
||||||
```
|
```
|
||||||
|
|
||||||
```python sync
|
```python sync
|
||||||
|
|
@ -55,7 +55,7 @@ page = context.new_page()
|
||||||
page.goto("https://playwright.dev")
|
page.goto("https://playwright.dev")
|
||||||
|
|
||||||
# Stop tracing and export it into a zip archive.
|
# Stop tracing and export it into a zip archive.
|
||||||
context.tracing.stop(path = "trace.pwtrace.zip")
|
context.tracing.stop(path = "trace.zip")
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
@ -80,22 +80,22 @@ page.navigate("https://playwright.dev");
|
||||||
|
|
||||||
// Stop tracing and export it into a zip archive.
|
// Stop tracing and export it into a zip archive.
|
||||||
context.tracing().stop(new Tracing.StopOptions()
|
context.tracing().stop(new Tracing.StopOptions()
|
||||||
.setPath(Paths.get("trace.pwtrace.zip")));
|
.setPath(Paths.get("trace.zip")));
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
This will record the trace and place it into the file named `trace.pwtrace.zip`.
|
This will record the trace and place it into the file named `trace.zip`.
|
||||||
|
|
||||||
## Opening the trace
|
## Opening the trace
|
||||||
|
|
||||||
You can open the saved trace using the Playwright CLI or in your browser on [`trace.playwright.dev`](https://trace.playwright.dev). Make sure to add the full path to where your trace's zip file is located. Once opened you can click on each action or use the timeline to see the state of the page before and after each action. You can also inspect the log, source and network during each step of the test. The trace viewer creates a DOM snapshot so you can fully interact with it, open devtools etc.
|
You can open the saved trace using the Playwright CLI or in your browser on [`trace.playwright.dev`](https://trace.playwright.dev). Make sure to add the full path to where your trace's zip file is located. Once opened you can click on each action or use the timeline to see the state of the page before and after each action. You can also inspect the log, source and network during each step of the test. The trace viewer creates a DOM snapshot so you can fully interact with it, open devtools etc.
|
||||||
|
|
||||||
```bash java
|
```bash java
|
||||||
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="show-trace trace.pwtrace.zip"
|
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="show-trace trace.zip"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash python
|
```bash python
|
||||||
playwright show-trace trace.pwtrace.zip
|
playwright show-trace trace.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
######
|
######
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ Playwright Trace Viewer is a GUI tool that lets you explore recorded Playwright
|
||||||
|
|
||||||
## Recording a Trace
|
## Recording a Trace
|
||||||
|
|
||||||
By default the [playwright.config](./trace-viewer.md#recording-a-trace-on-ci) file will contain the configuration needed to create a `trace.pwtrace.zip` file for each test. Traces are setup to run `on-first-retry` meaning they will be run on the first retry of a failed test. Also `retries` are set to 2 when running on CI and 0 locally. This means the traces will be recorded on the first retry of a failed test but not on the first run and not on the second retry.
|
By default the [playwright.config](./trace-viewer.md#recording-a-trace-on-ci) file will contain the configuration needed to create a `trace.zip` file for each test. Traces are setup to run `on-first-retry` meaning they will be run on the first retry of a failed test. Also `retries` are set to 2 when running on CI and 0 locally. This means the traces will be recorded on the first retry of a failed test but not on the first run and not on the second retry.
|
||||||
|
|
||||||
```js title="playwright.config.ts"
|
```js title="playwright.config.ts"
|
||||||
import { defineConfig } from '@playwright/test';
|
import { defineConfig } from '@playwright/test';
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ npx playwright show-report
|
||||||
* langs: js
|
* langs: js
|
||||||
|
|
||||||
Traces should be run on continuous integration on the first retry of a failed test
|
Traces should be run on continuous integration on the first retry of a failed test
|
||||||
by setting the `trace: 'on-first-retry'` option in the test configuration file. This will produce a `trace.pwtrace.zip` file for each test that was retried.
|
by setting the `trace: 'on-first-retry'` option in the test configuration file. This will produce a `trace.zip` file for each test that was retried.
|
||||||
|
|
||||||
```js tab=js-test title="playwright.config.ts"
|
```js tab=js-test title="playwright.config.ts"
|
||||||
import { defineConfig } from '@playwright/test';
|
import { defineConfig } from '@playwright/test';
|
||||||
|
|
@ -155,7 +155,7 @@ const page = await context.newPage();
|
||||||
await page.goto('https://playwright.dev');
|
await page.goto('https://playwright.dev');
|
||||||
|
|
||||||
// Stop tracing and export it into a zip archive.
|
// Stop tracing and export it into a zip archive.
|
||||||
await context.tracing.stop({ path: 'trace.pwtrace.zip' });
|
await context.tracing.stop({ path: 'trace.zip' });
|
||||||
```
|
```
|
||||||
|
|
||||||
Available options to record a trace:
|
Available options to record a trace:
|
||||||
|
|
@ -185,7 +185,7 @@ Options for tracing are:
|
||||||
- `off`: Do not record trace. (default)
|
- `off`: Do not record trace. (default)
|
||||||
- `retain-on-failure`: Record trace for each test, but remove all traces from successful test runs.
|
- `retain-on-failure`: Record trace for each test, but remove all traces from successful test runs.
|
||||||
|
|
||||||
This will record the trace and place it into the file named `trace.pwtrace.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>
|
<details>
|
||||||
<summary>If you are not using Pytest, click here to learn how to record traces.</summary>
|
<summary>If you are not using Pytest, click here to learn how to record traces.</summary>
|
||||||
|
|
@ -201,7 +201,7 @@ page = await context.new_page()
|
||||||
await page.goto("https://playwright.dev")
|
await page.goto("https://playwright.dev")
|
||||||
|
|
||||||
# Stop tracing and export it into a zip archive.
|
# Stop tracing and export it into a zip archive.
|
||||||
await context.tracing.stop(path = "trace.pwtrace.zip")
|
await context.tracing.stop(path = "trace.zip")
|
||||||
```
|
```
|
||||||
|
|
||||||
```python sync
|
```python sync
|
||||||
|
|
@ -215,7 +215,7 @@ page = context.new_page()
|
||||||
page.goto("https://playwright.dev")
|
page.goto("https://playwright.dev")
|
||||||
|
|
||||||
# Stop tracing and export it into a zip archive.
|
# Stop tracing and export it into a zip archive.
|
||||||
context.tracing.stop(path = "trace.pwtrace.zip")
|
context.tracing.stop(path = "trace.zip")
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
@ -240,10 +240,10 @@ page.navigate("https://playwright.dev");
|
||||||
|
|
||||||
// Stop tracing and export it into a zip archive.
|
// Stop tracing and export it into a zip archive.
|
||||||
context.tracing().stop(new Tracing.StopOptions()
|
context.tracing().stop(new Tracing.StopOptions()
|
||||||
.setPath(Paths.get("trace.pwtracezip")));
|
.setPath(Paths.get("trace.zip")));
|
||||||
```
|
```
|
||||||
|
|
||||||
This will record the trace and place it into the file named `trace.pwtrace.zip`.
|
This will record the trace and place it into the file named `trace.zip`.
|
||||||
|
|
||||||
## Recording a trace
|
## Recording a trace
|
||||||
* langs: csharp
|
* langs: csharp
|
||||||
|
|
@ -466,22 +466,22 @@ public class ExampleTest : PageTest
|
||||||
|
|
||||||
## Opening the trace
|
## Opening the trace
|
||||||
|
|
||||||
You can open the saved trace using the Playwright CLI or in your browser on [`trace.playwright.dev`](https://trace.playwright.dev). Make sure to add the full path to where your `trace.pwtrace.zip` file is located.
|
You can open the saved trace using the Playwright CLI or in your browser on [`trace.playwright.dev`](https://trace.playwright.dev). Make sure to add the full path to where your `trace.zip` file is located.
|
||||||
|
|
||||||
```bash js
|
```bash js
|
||||||
npx playwright show-trace path/to/trace.pwtrace.zip
|
npx playwright show-trace path/to/trace.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash java
|
```bash java
|
||||||
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="show-trace trace.pwtrace.zip"
|
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="show-trace trace.zip"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash python
|
```bash python
|
||||||
playwright show-trace trace.pwtrace.zip
|
playwright show-trace trace.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash csharp
|
```bash csharp
|
||||||
pwsh bin/Debug/netX/playwright.ps1 show-trace trace.pwtrace.zip
|
pwsh bin/Debug/netX/playwright.ps1 show-trace trace.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
## Using [trace.playwright.dev](https://trace.playwright.dev)
|
## Using [trace.playwright.dev](https://trace.playwright.dev)
|
||||||
|
|
@ -496,19 +496,19 @@ pwsh bin/Debug/netX/playwright.ps1 show-trace trace.pwtrace.zip
|
||||||
You can open remote traces using its URL. They could be generated on a CI run which makes it easy to view the remote trace without having to manually download the file.
|
You can open remote traces using its URL. They could be generated on a CI run which makes it easy to view the remote trace without having to manually download the file.
|
||||||
|
|
||||||
```bash js
|
```bash js
|
||||||
npx playwright show-trace https://example.com/trace.pwtrace.zip
|
npx playwright show-trace https://example.com/trace.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash java
|
```bash java
|
||||||
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="show-trace https://example.com/trace.pwtrace.zip"
|
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="show-trace https://example.com/trace.zip"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash python
|
```bash python
|
||||||
playwright show-trace https://example.com/trace.pwtrace.zip
|
playwright show-trace https://example.com/trace.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash csharp
|
```bash csharp
|
||||||
pwsh bin/Debug/netX/playwright.ps1 show-trace https://example.com/trace.pwtrace.zip
|
pwsh bin/Debug/netX/playwright.ps1 show-trace https://example.com/trace.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@
|
||||||
flex: none;
|
flex: none;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 8px 8px;
|
padding: 0 8px 8px;
|
||||||
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-case-path {
|
.test-case-path {
|
||||||
|
|
|
||||||
|
|
@ -81,9 +81,9 @@ test('should render copy buttons for annotations', async ({ mount, page, context
|
||||||
|
|
||||||
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={testCase} run={0} anchor=''></TestCaseView>);
|
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={testCase} run={0} anchor=''></TestCaseView>);
|
||||||
await expect(component.getByText('Annotation text', { exact: false }).first()).toBeVisible();
|
await expect(component.getByText('Annotation text', { exact: false }).first()).toBeVisible();
|
||||||
component.getByText('Annotation text', { exact: false }).first().hover();
|
await component.getByText('Annotation text', { exact: false }).first().hover();
|
||||||
await expect(component.getByLabel('Copy to clipboard').first()).toBeVisible();
|
await expect(component.locator('.test-case-annotation').getByLabel('Copy to clipboard').first()).toBeVisible();
|
||||||
await component.getByLabel('Copy to clipboard').first().click();
|
await component.locator('.test-case-annotation').getByLabel('Copy to clipboard').first().click();
|
||||||
const handle = await page.evaluateHandle(() => navigator.clipboard.readText());
|
const handle = await page.evaluateHandle(() => navigator.clipboard.readText());
|
||||||
const clipboardContent = await handle.jsonValue();
|
const clipboardContent = await handle.jsonValue();
|
||||||
expect(clipboardContent).toBe('Annotation text');
|
expect(clipboardContent).toBe('Annotation text');
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,11 @@ export const TestCaseView: React.FC<{
|
||||||
{test && <div className='test-case-path'>{test.path.join(' › ')}</div>}
|
{test && <div className='test-case-path'>{test.path.join(' › ')}</div>}
|
||||||
{test && <div className='test-case-title'>{test?.title}</div>}
|
{test && <div className='test-case-title'>{test?.title}</div>}
|
||||||
{test && <div className='hbox'>
|
{test && <div className='hbox'>
|
||||||
<div className='test-case-location'>{test.location.file}:{test.location.line}</div>
|
<div className='test-case-location'>
|
||||||
|
<CopyToClipboardContainer value={`${test?.location.file}:${test?.location.line}`}>
|
||||||
|
{test.location.file}:{test.location.line}
|
||||||
|
</CopyToClipboardContainer>
|
||||||
|
</div>
|
||||||
<div style={{ flex: 'auto' }}></div>
|
<div style={{ flex: 'auto' }}></div>
|
||||||
<div className='test-case-duration'>{msToString(test.duration)}</div>
|
<div className='test-case-duration'>{msToString(test.duration)}</div>
|
||||||
</div>}
|
</div>}
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,7 @@ program
|
||||||
}).addHelpText('afterAll', `
|
}).addHelpText('afterAll', `
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
$ show-trace https://example.com/trace.pwtrace.zip`);
|
$ show-trace https://example.com/trace.zip`);
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
browser: string;
|
browser: string;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import { HttpsProxyAgent, SocksProxyAgent } from '../utilsBundle';
|
||||||
import { BrowserContext, verifyClientCertificates } from './browserContext';
|
import { BrowserContext, verifyClientCertificates } from './browserContext';
|
||||||
import { CookieStore, domainMatches, parseRawCookie } from './cookieStore';
|
import { CookieStore, domainMatches, parseRawCookie } from './cookieStore';
|
||||||
import { MultipartFormData } from './formData';
|
import { MultipartFormData } from './formData';
|
||||||
import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from '../utils/happy-eyeballs';
|
import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent, timingForSocket } from '../utils/happy-eyeballs';
|
||||||
import type { CallMetadata } from './instrumentation';
|
import type { CallMetadata } from './instrumentation';
|
||||||
import { SdkObject } from './instrumentation';
|
import { SdkObject } from './instrumentation';
|
||||||
import type { Playwright } from './playwright';
|
import type { Playwright } from './playwright';
|
||||||
|
|
@ -40,6 +40,7 @@ import { Tracing } from './trace/recorder/tracing';
|
||||||
import type * as types from './types';
|
import type * as types from './types';
|
||||||
import type { HeadersArray, ProxySettings } from './types';
|
import type { HeadersArray, ProxySettings } from './types';
|
||||||
import { getMatchingTLSOptionsForOrigin, rewriteOpenSSLErrorIfNeeded } from './socksClientCertificatesInterceptor';
|
import { getMatchingTLSOptionsForOrigin, rewriteOpenSSLErrorIfNeeded } from './socksClientCertificatesInterceptor';
|
||||||
|
import type * as har from '@trace/har';
|
||||||
|
|
||||||
type FetchRequestOptions = {
|
type FetchRequestOptions = {
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
|
|
@ -71,6 +72,7 @@ export type APIRequestFinishedEvent = {
|
||||||
statusCode: number;
|
statusCode: number;
|
||||||
statusMessage: string;
|
statusMessage: string;
|
||||||
body?: Buffer;
|
body?: Buffer;
|
||||||
|
timings: har.Timings;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SendRequestOptions = https.RequestOptions & {
|
type SendRequestOptions = https.RequestOptions & {
|
||||||
|
|
@ -294,8 +296,28 @@ export abstract class APIRequestContext extends SdkObject {
|
||||||
// If we have a proxy agent already, do not override it.
|
// If we have a proxy agent already, do not override it.
|
||||||
const agent = options.agent || (url.protocol === 'https:' ? httpsHappyEyeballsAgent : httpHappyEyeballsAgent);
|
const agent = options.agent || (url.protocol === 'https:' ? httpsHappyEyeballsAgent : httpHappyEyeballsAgent);
|
||||||
const requestOptions = { ...options, agent };
|
const requestOptions = { ...options, agent };
|
||||||
|
|
||||||
|
const startAt = monotonicTime();
|
||||||
|
let dnsLookupAt: number | undefined;
|
||||||
|
let tcpConnectionAt: number | undefined;
|
||||||
|
let tlsHandshakeAt: number | undefined;
|
||||||
|
let requestFinishAt: number | undefined;
|
||||||
|
|
||||||
const request = requestConstructor(url, requestOptions as any, async response => {
|
const request = requestConstructor(url, requestOptions as any, async response => {
|
||||||
|
const responseAt = monotonicTime();
|
||||||
const notifyRequestFinished = (body?: Buffer) => {
|
const notifyRequestFinished = (body?: Buffer) => {
|
||||||
|
const endAt = monotonicTime();
|
||||||
|
// spec: http://www.softwareishard.com/blog/har-12-spec/#timings
|
||||||
|
const timings: har.Timings = {
|
||||||
|
send: requestFinishAt! - startAt,
|
||||||
|
wait: responseAt - requestFinishAt!,
|
||||||
|
receive: endAt - responseAt,
|
||||||
|
dns: dnsLookupAt ? dnsLookupAt - startAt : -1,
|
||||||
|
connect: (tlsHandshakeAt ?? tcpConnectionAt!) - startAt, // "If [ssl] is defined then the time is also included in the connect field "
|
||||||
|
ssl: tlsHandshakeAt ? tlsHandshakeAt - tcpConnectionAt! : -1,
|
||||||
|
blocked: -1,
|
||||||
|
};
|
||||||
|
|
||||||
const requestFinishedEvent: APIRequestFinishedEvent = {
|
const requestFinishedEvent: APIRequestFinishedEvent = {
|
||||||
requestEvent,
|
requestEvent,
|
||||||
httpVersion: response.httpVersion,
|
httpVersion: response.httpVersion,
|
||||||
|
|
@ -304,7 +326,8 @@ export abstract class APIRequestContext extends SdkObject {
|
||||||
headers: response.headers,
|
headers: response.headers,
|
||||||
rawHeaders: response.rawHeaders,
|
rawHeaders: response.rawHeaders,
|
||||||
cookies,
|
cookies,
|
||||||
body
|
body,
|
||||||
|
timings,
|
||||||
};
|
};
|
||||||
this.emit(APIRequestContext.Events.RequestFinished, requestFinishedEvent);
|
this.emit(APIRequestContext.Events.RequestFinished, requestFinishedEvent);
|
||||||
};
|
};
|
||||||
|
|
@ -450,6 +473,19 @@ export abstract class APIRequestContext extends SdkObject {
|
||||||
this.on(APIRequestContext.Events.Dispose, disposeListener);
|
this.on(APIRequestContext.Events.Dispose, disposeListener);
|
||||||
request.on('close', () => this.off(APIRequestContext.Events.Dispose, disposeListener));
|
request.on('close', () => this.off(APIRequestContext.Events.Dispose, disposeListener));
|
||||||
|
|
||||||
|
request.on('socket', socket => {
|
||||||
|
// happy eyeballs don't emit lookup and connect events, so we use our custom ones
|
||||||
|
const happyEyeBallsTimings = timingForSocket(socket);
|
||||||
|
dnsLookupAt = happyEyeBallsTimings.dnsLookupAt;
|
||||||
|
tcpConnectionAt = happyEyeBallsTimings.tcpConnectionAt;
|
||||||
|
|
||||||
|
// non-happy-eyeballs sockets
|
||||||
|
socket.on('lookup', () => { dnsLookupAt = monotonicTime(); });
|
||||||
|
socket.on('connect', () => { tcpConnectionAt = monotonicTime(); });
|
||||||
|
socket.on('secureConnect', () => { tlsHandshakeAt = monotonicTime(); });
|
||||||
|
});
|
||||||
|
request.on('finish', () => { requestFinishAt = monotonicTime(); });
|
||||||
|
|
||||||
progress.log(`→ ${options.method} ${url.toString()}`);
|
progress.log(`→ ${options.method} ${url.toString()}`);
|
||||||
if (options.headers) {
|
if (options.headers) {
|
||||||
for (const [name, value] of Object.entries(options.headers))
|
for (const [name, value] of Object.entries(options.headers))
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,12 @@ export class HarTracer {
|
||||||
harEntry.response.statusText = event.statusMessage;
|
harEntry.response.statusText = event.statusMessage;
|
||||||
harEntry.response.httpVersion = event.httpVersion;
|
harEntry.response.httpVersion = event.httpVersion;
|
||||||
harEntry.response.redirectURL = event.headers.location || '';
|
harEntry.response.redirectURL = event.headers.location || '';
|
||||||
|
|
||||||
|
if (!this._options.omitTiming) {
|
||||||
|
harEntry.timings = event.timings;
|
||||||
|
this._computeHarEntryTotalTime(harEntry);
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < event.rawHeaders.length; i += 2) {
|
for (let i = 0; i < event.rawHeaders.length; i += 2) {
|
||||||
harEntry.response.headers.push({
|
harEntry.response.headers.push({
|
||||||
name: event.rawHeaders[i],
|
name: event.rawHeaders[i],
|
||||||
|
|
|
||||||
|
|
@ -310,7 +310,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
|
|
||||||
this._fs.copyFile(this._state.networkFile, newNetworkFile);
|
this._fs.copyFile(this._state.networkFile, newNetworkFile);
|
||||||
|
|
||||||
const zipFileName = this._state.traceFile + '.pwtrace.zip';
|
const zipFileName = this._state.traceFile + '.zip';
|
||||||
if (params.mode === 'archive')
|
if (params.mode === 'archive')
|
||||||
this._fs.zip(entries, zipFileName);
|
this._fs.zip(entries, zipFileName);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import * as net from 'net';
|
||||||
import * as tls from 'tls';
|
import * as tls from 'tls';
|
||||||
import { ManualPromise } from './manualPromise';
|
import { ManualPromise } from './manualPromise';
|
||||||
import { assert } from './debug';
|
import { assert } from './debug';
|
||||||
|
import { monotonicTime } from './time';
|
||||||
|
|
||||||
// Implementation(partial) of Happy Eyeballs 2 algorithm described in
|
// Implementation(partial) of Happy Eyeballs 2 algorithm described in
|
||||||
// https://www.rfc-editor.org/rfc/rfc8305
|
// https://www.rfc-editor.org/rfc/rfc8305
|
||||||
|
|
@ -28,6 +29,9 @@ import { assert } from './debug';
|
||||||
// Same as in Chromium (https://source.chromium.org/chromium/chromium/src/+/5666ff4f5077a7e2f72902f3a95f5d553ea0d88d:net/socket/transport_connect_job.cc;l=102)
|
// Same as in Chromium (https://source.chromium.org/chromium/chromium/src/+/5666ff4f5077a7e2f72902f3a95f5d553ea0d88d:net/socket/transport_connect_job.cc;l=102)
|
||||||
const connectionAttemptDelayMs = 300;
|
const connectionAttemptDelayMs = 300;
|
||||||
|
|
||||||
|
const kDNSLookupAt = Symbol('kDNSLookupAt')
|
||||||
|
const kTCPConnectionAt = Symbol('kTCPConnectionAt')
|
||||||
|
|
||||||
class HttpHappyEyeballsAgent extends http.Agent {
|
class HttpHappyEyeballsAgent extends http.Agent {
|
||||||
createConnection(options: http.ClientRequestArgs, oncreate?: (err: Error | null, socket?: net.Socket) => void): net.Socket | undefined {
|
createConnection(options: http.ClientRequestArgs, oncreate?: (err: Error | null, socket?: net.Socket) => void): net.Socket | undefined {
|
||||||
// There is no ambiguity in case of IP address.
|
// There is no ambiguity in case of IP address.
|
||||||
|
|
@ -107,6 +111,7 @@ export async function createConnectionAsync(
|
||||||
const lookup = (options as any).__testHookLookup || lookupAddresses;
|
const lookup = (options as any).__testHookLookup || lookupAddresses;
|
||||||
const hostname = clientRequestArgsToHostName(options);
|
const hostname = clientRequestArgsToHostName(options);
|
||||||
const addresses = await lookup(hostname);
|
const addresses = await lookup(hostname);
|
||||||
|
const dnsLookupAt = monotonicTime();
|
||||||
const sockets = new Set<net.Socket>();
|
const sockets = new Set<net.Socket>();
|
||||||
let firstError;
|
let firstError;
|
||||||
let errorCount = 0;
|
let errorCount = 0;
|
||||||
|
|
@ -132,9 +137,13 @@ export async function createConnectionAsync(
|
||||||
port: options.port as number,
|
port: options.port as number,
|
||||||
host: address });
|
host: address });
|
||||||
|
|
||||||
|
(socket as any)[kDNSLookupAt] = dnsLookupAt;
|
||||||
|
|
||||||
// Each socket may fire only one of 'connect', 'timeout' or 'error' events.
|
// Each socket may fire only one of 'connect', 'timeout' or 'error' events.
|
||||||
// None of these events are fired after socket.destroy() is called.
|
// None of these events are fired after socket.destroy() is called.
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
|
(socket as any)[kTCPConnectionAt] = monotonicTime();
|
||||||
|
|
||||||
connected.resolve();
|
connected.resolve();
|
||||||
oncreate?.(null, socket);
|
oncreate?.(null, socket);
|
||||||
// TODO: Cache the result?
|
// TODO: Cache the result?
|
||||||
|
|
@ -189,3 +198,9 @@ function clientRequestArgsToHostName(options: http.ClientRequestArgs): string {
|
||||||
throw new Error('Either options.hostname or options.host must be provided');
|
throw new Error('Either options.hostname or options.host must be provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function timingForSocket(socket: net.Socket | tls.TLSSocket) {
|
||||||
|
return {
|
||||||
|
dnsLookupAt: (socket as any)[kDNSLookupAt] as number | undefined,
|
||||||
|
tcpConnectionAt: (socket as any)[kTCPConnectionAt] as number | undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
8
packages/playwright-core/types/types.d.ts
vendored
8
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -19927,7 +19927,7 @@ export interface Touchscreen {
|
||||||
* await context.tracing.start({ screenshots: true, snapshots: true });
|
* await context.tracing.start({ screenshots: true, snapshots: true });
|
||||||
* const page = await context.newPage();
|
* const page = await context.newPage();
|
||||||
* await page.goto('https://playwright.dev');
|
* await page.goto('https://playwright.dev');
|
||||||
* await context.tracing.stop({ path: 'trace.pwtrace.zip' });
|
* await context.tracing.stop({ path: 'trace.zip' });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
@ -19941,7 +19941,7 @@ export interface Tracing {
|
||||||
* await context.tracing.start({ screenshots: true, snapshots: true });
|
* await context.tracing.start({ screenshots: true, snapshots: true });
|
||||||
* const page = await context.newPage();
|
* const page = await context.newPage();
|
||||||
* await page.goto('https://playwright.dev');
|
* await page.goto('https://playwright.dev');
|
||||||
* await context.tracing.stop({ path: 'trace.pwtrace.zip' });
|
* await context.tracing.stop({ path: 'trace.zip' });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param options
|
* @param options
|
||||||
|
|
@ -19996,12 +19996,12 @@ export interface Tracing {
|
||||||
* await context.tracing.startChunk();
|
* await context.tracing.startChunk();
|
||||||
* await page.getByText('Get Started').click();
|
* await page.getByText('Get Started').click();
|
||||||
* // Everything between startChunk and stopChunk will be recorded in the trace.
|
* // Everything between startChunk and stopChunk will be recorded in the trace.
|
||||||
* await context.tracing.stopChunk({ path: 'trace1.pwtrace.zip' });
|
* await context.tracing.stopChunk({ path: 'trace1.zip' });
|
||||||
*
|
*
|
||||||
* await context.tracing.startChunk();
|
* await context.tracing.startChunk();
|
||||||
* await page.goto('http://example.com');
|
* await page.goto('http://example.com');
|
||||||
* // Save a second trace file with different actions.
|
* // Save a second trace file with different actions.
|
||||||
* await context.tracing.stopChunk({ path: 'trace2.pwtrace.zip' });
|
* await context.tracing.stopChunk({ path: 'trace2.zip' });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param options
|
* @param options
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
"@babel/code-frame": "^7.24.2",
|
"@babel/code-frame": "^7.24.2",
|
||||||
"@babel/core": "^7.24.4",
|
"@babel/core": "^7.24.4",
|
||||||
"@babel/helper-plugin-utils": "^7.24.0",
|
"@babel/helper-plugin-utils": "^7.24.0",
|
||||||
"@babel/parser": "^7.24.4",
|
|
||||||
"@babel/plugin-proposal-decorators": "^7.24.1",
|
"@babel/plugin-proposal-decorators": "^7.24.1",
|
||||||
"@babel/plugin-proposal-explicit-resource-management": "^7.24.1",
|
"@babel/plugin-proposal-explicit-resource-management": "^7.24.1",
|
||||||
"@babel/plugin-syntax-async-generators": "^7.8.4",
|
"@babel/plugin-syntax-async-generators": "^7.8.4",
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
"@babel/code-frame": "^7.24.2",
|
"@babel/code-frame": "^7.24.2",
|
||||||
"@babel/core": "^7.24.4",
|
"@babel/core": "^7.24.4",
|
||||||
"@babel/helper-plugin-utils": "^7.24.0",
|
"@babel/helper-plugin-utils": "^7.24.0",
|
||||||
"@babel/parser": "^7.24.4",
|
|
||||||
"@babel/plugin-proposal-decorators": "^7.24.1",
|
"@babel/plugin-proposal-decorators": "^7.24.1",
|
||||||
"@babel/plugin-proposal-explicit-resource-management": "^7.24.1",
|
"@babel/plugin-proposal-explicit-resource-management": "^7.24.1",
|
||||||
"@babel/plugin-syntax-async-generators": "^7.8.4",
|
"@babel/plugin-syntax-async-generators": "^7.8.4",
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import * as babel from '@babel/core';
|
||||||
export { codeFrameColumns } from '@babel/code-frame';
|
export { codeFrameColumns } from '@babel/code-frame';
|
||||||
export { declare } from '@babel/helper-plugin-utils';
|
export { declare } from '@babel/helper-plugin-utils';
|
||||||
export { types } from '@babel/core';
|
export { types } from '@babel/core';
|
||||||
export { parse } from '@babel/parser';
|
|
||||||
import traverseFunction from '@babel/traverse';
|
import traverseFunction from '@babel/traverse';
|
||||||
export const traverse = traverseFunction;
|
export const traverse = traverseFunction;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -259,11 +259,11 @@ export class TestTypeImpl {
|
||||||
suite._use.push({ fixtures, location });
|
suite._use.push({ fixtures, location });
|
||||||
}
|
}
|
||||||
|
|
||||||
async _step<T>(title: string, body: () => Promise<T>, options: { box?: boolean } = {}): Promise<T> {
|
async _step<T>(title: string, body: () => Promise<T>, options: {box?: boolean, location?: Location } = {}): Promise<T> {
|
||||||
const testInfo = currentTestInfo();
|
const testInfo = currentTestInfo();
|
||||||
if (!testInfo)
|
if (!testInfo)
|
||||||
throw new Error(`test.step() can only be called from a test`);
|
throw new Error(`test.step() can only be called from a test`);
|
||||||
const step = testInfo._addStep({ category: 'test.step', title, box: options.box });
|
const step = testInfo._addStep({ category: 'test.step', title, location: options.location, box: options.box });
|
||||||
return await zones.run('stepZone', step, async () => {
|
return await zones.run('stepZone', step, async () => {
|
||||||
try {
|
try {
|
||||||
const result = await body();
|
const result = await body();
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import type { BabelFileResult } from '../../bundles/babel/node_modules/@types/ba
|
||||||
export const codeFrameColumns: typeof import('../../bundles/babel/node_modules/@types/babel__code-frame').codeFrameColumns = require('./babelBundleImpl').codeFrameColumns;
|
export const codeFrameColumns: typeof import('../../bundles/babel/node_modules/@types/babel__code-frame').codeFrameColumns = require('./babelBundleImpl').codeFrameColumns;
|
||||||
export const declare: typeof import('../../bundles/babel/node_modules/@types/babel__helper-plugin-utils').declare = require('./babelBundleImpl').declare;
|
export const declare: typeof import('../../bundles/babel/node_modules/@types/babel__helper-plugin-utils').declare = require('./babelBundleImpl').declare;
|
||||||
export const types: typeof import('../../bundles/babel/node_modules/@types/babel__core').types = require('./babelBundleImpl').types;
|
export const types: typeof import('../../bundles/babel/node_modules/@types/babel__core').types = require('./babelBundleImpl').types;
|
||||||
export const parse: typeof import('../../bundles/babel/node_modules/@babel/parser/typings/babel-parser').parse = require('./babelBundleImpl').parse;
|
|
||||||
export const traverse: typeof import('../../bundles/babel/node_modules/@types/babel__traverse').default = require('./babelBundleImpl').traverse;
|
export const traverse: typeof import('../../bundles/babel/node_modules/@types/babel__traverse').default = require('./babelBundleImpl').traverse;
|
||||||
export type BabelPlugin = [string, any?];
|
export type BabelPlugin = [string, any?];
|
||||||
export type BabelTransformFunction = (code: string, filename: string, isTypeScript: boolean, isModule: boolean, pluginsPrefix: BabelPlugin[], pluginsSuffix: BabelPlugin[]) => BabelFileResult;
|
export type BabelTransformFunction = (code: string, filename: string, isTypeScript: boolean, isModule: boolean, pluginsPrefix: BabelPlugin[], pluginsSuffix: BabelPlugin[]) => BabelFileResult;
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ export class TestTracing {
|
||||||
}
|
}
|
||||||
|
|
||||||
generateNextTraceRecordingPath() {
|
generateNextTraceRecordingPath() {
|
||||||
const file = path.join(this._artifactsDir, createGuid() + '.pwtrace.zip');
|
const file = path.join(this._artifactsDir, createGuid() + '.zip');
|
||||||
this._temporaryTraceFiles.push(file);
|
this._temporaryTraceFiles.push(file);
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
@ -214,7 +214,7 @@ export class TestTracing {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const tracePath = this._testInfo.outputPath('trace.pwtrace.zip');
|
const tracePath = this._testInfo.outputPath('trace.zip');
|
||||||
await mergeTraceFiles(tracePath, this._temporaryTraceFiles);
|
await mergeTraceFiles(tracePath, this._temporaryTraceFiles);
|
||||||
this._testInfo.attachments.push({ name: 'trace', path: tracePath, contentType: 'application/zip' });
|
this._testInfo.attachments.push({ name: 'trace', path: tracePath, contentType: 'application/zip' });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
packages/playwright/types/test.d.ts
vendored
2
packages/playwright/types/test.d.ts
vendored
|
|
@ -4718,7 +4718,7 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
|
||||||
* @param body Step body.
|
* @param body Step body.
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
step<T>(title: string, body: () => T | Promise<T>, options?: { box?: boolean }): Promise<T>;
|
step<T>(title: string, body: () => T | Promise<T>, options?: { box?: boolean, location?: Location }): Promise<T>;
|
||||||
/**
|
/**
|
||||||
* `expect` function can be used to create test assertions. Read more about [test assertions](https://playwright.dev/docs/test-assertions).
|
* `expect` function can be used to create test assertions. Read more about [test assertions](https://playwright.dev/docs/test-assertions).
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ export const traceViewerFixtures: Fixtures<TraceViewerFixtures, {}, BaseTestFixt
|
||||||
|
|
||||||
runAndTrace: async ({ context, showTraceViewer }, use, testInfo) => {
|
runAndTrace: async ({ context, showTraceViewer }, use, testInfo) => {
|
||||||
await use(async (body: () => Promise<void>, optsOverrides = {}) => {
|
await use(async (body: () => Promise<void>, optsOverrides = {}) => {
|
||||||
const traceFile = testInfo.outputPath('trace.pwtrace.zip');
|
const traceFile = testInfo.outputPath('trace.zip');
|
||||||
await context.tracing.start({ snapshots: true, screenshots: true, sources: true, ...optsOverrides });
|
await context.tracing.start({ snapshots: true, screenshots: true, sources: true, ...optsOverrides });
|
||||||
await body();
|
await body();
|
||||||
await context.tracing.stop({ path: traceFile });
|
await context.tracing.stop({ path: traceFile });
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ test('should work when wrapped inside @playwright/test and trace is enabled', as
|
||||||
await expect(window).toHaveTitle(/Playwright/);
|
await expect(window).toHaveTitle(/Playwright/);
|
||||||
await expect(window.getByRole('heading')).toHaveText('Playwright');
|
await expect(window.getByRole('heading')).toHaveText('Playwright');
|
||||||
|
|
||||||
const path = test.info().outputPath('electron-trace.pwtrace.zip');
|
const path = test.info().outputPath('electron-trace.zip');
|
||||||
if (trace) {
|
if (trace) {
|
||||||
await window.context().tracing.stop({ path });
|
await window.context().tracing.stop({ path });
|
||||||
test.info().attachments.push({ name: 'trace', path, contentType: 'application/zip' });
|
test.info().attachments.push({ name: 'trace', path, contentType: 'application/zip' });
|
||||||
|
|
@ -73,9 +73,9 @@ test('should work when wrapped inside @playwright/test and trace is enabled', as
|
||||||
});
|
});
|
||||||
const traces = [
|
const traces = [
|
||||||
// our actual trace.
|
// our actual trace.
|
||||||
path.join(tmpWorkspace, 'test-results', 'electron-with-tracing-should-work', 'electron-trace.pwtrace.zip'),
|
path.join(tmpWorkspace, 'test-results', 'electron-with-tracing-should-work', 'electron-trace.zip'),
|
||||||
// contains the expect() calls
|
// contains the expect() calls
|
||||||
path.join(tmpWorkspace, 'test-results', 'electron-with-tracing-should-work', 'trace.pwtrace.zip'),
|
path.join(tmpWorkspace, 'test-results', 'electron-with-tracing-should-work', 'trace.zip'),
|
||||||
];
|
];
|
||||||
for (const trace of traces)
|
for (const trace of traces)
|
||||||
expect(fs.existsSync(trace)).toBe(true);
|
expect(fs.existsSync(trace)).toBe(true);
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ test('should reset tracing', async ({ reusedContext, trace }, testInfo) => {
|
||||||
page = context.pages()[0];
|
page = context.pages()[0];
|
||||||
await page.evaluate('2 + 2');
|
await page.evaluate('2 + 2');
|
||||||
|
|
||||||
const error = await context.tracing.stopChunk({ path: testInfo.outputPath('trace.pwtrace.zip') }).catch(e => e);
|
const error = await context.tracing.stopChunk({ path: testInfo.outputPath('trace.zip') }).catch(e => e);
|
||||||
expect(error.message).toContain('Must start tracing before stopping');
|
expect(error.message).toContain('Must start tracing before stopping');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -489,7 +489,7 @@ test('should allow tracing over cdp session', async ({ browserType, trace }, tes
|
||||||
await context.tracing.start({ screenshots: true, snapshots: true });
|
await context.tracing.start({ screenshots: true, snapshots: true });
|
||||||
const page = await context.newPage();
|
const page = await context.newPage();
|
||||||
await page.evaluate(() => 2 + 2);
|
await page.evaluate(() => 2 + 2);
|
||||||
const traceZip = testInfo.outputPath('trace.pwtrace.zip');
|
const traceZip = testInfo.outputPath('trace.zip');
|
||||||
await context.tracing.stop({ path: traceZip });
|
await context.tracing.stop({ path: traceZip });
|
||||||
await cdpBrowser.close();
|
await cdpBrowser.close();
|
||||||
expect(fs.existsSync(traceZip)).toBe(true);
|
expect(fs.existsSync(traceZip)).toBe(true);
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@ import type { Log } from '../../packages/trace/src/har';
|
||||||
import { parseHar } from '../config/utils';
|
import { parseHar } from '../config/utils';
|
||||||
const { createHttp2Server } = require('../../packages/playwright-core/lib/utils');
|
const { createHttp2Server } = require('../../packages/playwright-core/lib/utils');
|
||||||
|
|
||||||
async function pageWithHar(contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>, testInfo: any, options: { outputPath?: string, content?: 'embed' | 'attach' | 'omit', omitContent?: boolean } = {}) {
|
async function pageWithHar(contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>, testInfo: any, options: { outputPath?: string } & Partial<Pick<BrowserContextOptions['recordHar'], 'content' | 'omitContent' | 'mode'>> = {}) {
|
||||||
const harPath = testInfo.outputPath(options.outputPath || 'test.har');
|
const harPath = testInfo.outputPath(options.outputPath || 'test.har');
|
||||||
const context = await contextFactory({ recordHar: { path: harPath, content: options.content, omitContent: options.omitContent }, ignoreHTTPSErrors: true });
|
const context = await contextFactory({ recordHar: { path: harPath, ...options }, ignoreHTTPSErrors: true });
|
||||||
const page = await context.newPage();
|
const page = await context.newPage();
|
||||||
return {
|
return {
|
||||||
page,
|
page,
|
||||||
|
|
@ -820,6 +820,47 @@ it('should include API request', async ({ contextFactory, server }, testInfo) =>
|
||||||
expect(entry.response.headers.find(h => h.name.toLowerCase() === 'content-type')?.value).toContain('application/json');
|
expect(entry.response.headers.find(h => h.name.toLowerCase() === 'content-type')?.value).toContain('application/json');
|
||||||
expect(entry.response.content.size).toBe(15);
|
expect(entry.response.content.size).toBe(15);
|
||||||
expect(entry.response.content.text).toBe(responseBody.toString());
|
expect(entry.response.content.text).toBe(responseBody.toString());
|
||||||
|
|
||||||
|
expect(entry.time).toBeGreaterThan(0);
|
||||||
|
expect(entry.timings).toEqual(expect.objectContaining({
|
||||||
|
blocked: -1,
|
||||||
|
connect: expect.any(Number),
|
||||||
|
dns: expect.any(Number),
|
||||||
|
receive: expect.any(Number),
|
||||||
|
send: expect.any(Number),
|
||||||
|
ssl: expect.any(Number),
|
||||||
|
wait: expect.any(Number),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect minimal mode for API Requests', async ({ contextFactory, server }, testInfo) => {
|
||||||
|
const { page, getLog } = await pageWithHar(contextFactory, testInfo, { mode: 'minimal' });
|
||||||
|
const url = server.PREFIX + '/simple.json';
|
||||||
|
await page.request.post(url, {
|
||||||
|
headers: { cookie: 'a=b; c=d' },
|
||||||
|
data: { foo: 'bar' }
|
||||||
|
});
|
||||||
|
const { entries } = await getLog();
|
||||||
|
expect(entries).toHaveLength(1);
|
||||||
|
const [entry] = entries;
|
||||||
|
expect(entry.timings).toEqual({ receive: -1, send: -1, wait: -1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include redirects from API request', async ({ contextFactory, server }, testInfo) => {
|
||||||
|
server.setRedirect('/redirect-me', '/simple.json');
|
||||||
|
const { page, getLog } = await pageWithHar(contextFactory, testInfo);
|
||||||
|
await page.request.post(server.PREFIX + '/redirect-me', {
|
||||||
|
headers: { cookie: 'a=b; c=d' },
|
||||||
|
data: { foo: 'bar' }
|
||||||
|
});
|
||||||
|
const log = await getLog();
|
||||||
|
expect(log.entries.length).toBe(2);
|
||||||
|
const [redirect, json] = log.entries;
|
||||||
|
expect(redirect.request.url).toBe(server.PREFIX + '/redirect-me');
|
||||||
|
expect(json.request.url).toBe(server.PREFIX + '/simple.json');
|
||||||
|
|
||||||
|
expect(redirect.timings).toBeDefined();
|
||||||
|
expect(json.timings).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not hang on resources served from cache', async ({ contextFactory, server, browserName }, testInfo) => {
|
it('should not hang on resources served from cache', async ({ contextFactory, server, browserName }, testInfo) => {
|
||||||
|
|
|
||||||
|
|
@ -491,7 +491,7 @@ await page1.GotoAsync("about:blank?foo");`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should --save-trace', async ({ runCLI }, testInfo) => {
|
test('should --save-trace', async ({ runCLI }, testInfo) => {
|
||||||
const traceFileName = testInfo.outputPath('trace.pwtrace.zip');
|
const traceFileName = testInfo.outputPath('trace.zip');
|
||||||
const cli = runCLI([`--save-trace=${traceFileName}`], {
|
const cli = runCLI([`--save-trace=${traceFileName}`], {
|
||||||
autoExitWhen: ' ',
|
autoExitWhen: ' ',
|
||||||
});
|
});
|
||||||
|
|
@ -502,7 +502,7 @@ await page1.GotoAsync("about:blank?foo");`);
|
||||||
test('should save assets via SIGINT', async ({ runCLI, platform }, testInfo) => {
|
test('should save assets via SIGINT', async ({ runCLI, platform }, testInfo) => {
|
||||||
test.skip(platform === 'win32', 'SIGINT not supported on Windows');
|
test.skip(platform === 'win32', 'SIGINT not supported on Windows');
|
||||||
|
|
||||||
const traceFileName = testInfo.outputPath('trace.pwtrace.zip');
|
const traceFileName = testInfo.outputPath('trace.zip');
|
||||||
const storageFileName = testInfo.outputPath('auth.json');
|
const storageFileName = testInfo.outputPath('auth.json');
|
||||||
const harFileName = testInfo.outputPath('har.har');
|
const harFileName = testInfo.outputPath('har.har');
|
||||||
const cli = runCLI([`--save-trace=${traceFileName}`, `--save-storage=${storageFileName}`, `--save-har=${harFileName}`]);
|
const cli = runCLI([`--save-trace=${traceFileName}`, `--save-storage=${storageFileName}`, `--save-har=${harFileName}`]);
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ test.beforeAll(async function recordTrace({ browser, browserName, browserType, s
|
||||||
runBeforeCloseBrowserContext: async () => {
|
runBeforeCloseBrowserContext: async () => {
|
||||||
await page.hover('body');
|
await page.hover('body');
|
||||||
await page.close();
|
await page.close();
|
||||||
traceFile = path.join(workerInfo.project.outputDir, String(workerInfo.workerIndex), browserName, 'trace.pwtrace.zip');
|
traceFile = path.join(workerInfo.project.outputDir, String(workerInfo.workerIndex), browserName, 'trace.zip');
|
||||||
await context.tracing.stop({ path: traceFile });
|
await context.tracing.stop({ path: traceFile });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -698,7 +698,7 @@ test('should handle file URIs', async ({ page, runAndTrace, browserName }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should preserve currentSrc', async ({ browser, server, showTraceViewer }) => {
|
test('should preserve currentSrc', async ({ browser, server, showTraceViewer }) => {
|
||||||
const traceFile = test.info().outputPath('trace.pwtrace.zip');
|
const traceFile = test.info().outputPath('trace.zip');
|
||||||
const page = await browser.newPage({ deviceScaleFactor: 3 });
|
const page = await browser.newPage({ deviceScaleFactor: 3 });
|
||||||
await page.context().tracing.start({ snapshots: true, screenshots: true, sources: true });
|
await page.context().tracing.start({ snapshots: true, screenshots: true, sources: true });
|
||||||
await page.setViewportSize({ width: 300, height: 300 });
|
await page.setViewportSize({ width: 300, height: 300 });
|
||||||
|
|
@ -1294,7 +1294,7 @@ test('should highlight locator in iframe while typing', async ({ page, runAndTra
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should preserve noscript when javascript is disabled', async ({ browser, server, showTraceViewer }) => {
|
test('should preserve noscript when javascript is disabled', async ({ browser, server, showTraceViewer }) => {
|
||||||
const traceFile = test.info().outputPath('trace.pwtrace.zip');
|
const traceFile = test.info().outputPath('trace.zip');
|
||||||
const page = await browser.newPage({ javaScriptEnabled: false });
|
const page = await browser.newPage({ javaScriptEnabled: false });
|
||||||
await page.context().tracing.start({ snapshots: true, screenshots: true, sources: true });
|
await page.context().tracing.start({ snapshots: true, screenshots: true, sources: true });
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
|
@ -1311,8 +1311,8 @@ test('should preserve noscript when javascript is disabled', async ({ browser, s
|
||||||
await expect(frame.getByText('javascript is disabled!')).toBeVisible();
|
await expect(frame.getByText('javascript is disabled!')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should remove noscript by default', async ({ browser, server, showTraceViewer }) => {
|
test('should remove noscript by default', async ({ browser, server, showTraceViewer, browserType }) => {
|
||||||
const traceFile = test.info().outputPath('trace.pwtrace.zip');
|
const traceFile = test.info().outputPath('trace.zip');
|
||||||
const page = await browser.newPage({ javaScriptEnabled: undefined });
|
const page = await browser.newPage({ javaScriptEnabled: undefined });
|
||||||
await page.context().tracing.start({ snapshots: true, screenshots: true, sources: true });
|
await page.context().tracing.start({ snapshots: true, screenshots: true, sources: true });
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
|
@ -1329,8 +1329,8 @@ test('should remove noscript by default', async ({ browser, server, showTraceVie
|
||||||
await expect(frame.getByText('Enable JavaScript to run this app.')).toBeHidden();
|
await expect(frame.getByText('Enable JavaScript to run this app.')).toBeHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should remove noscript when javaScriptEnabled is set to true', async ({ browser, server, showTraceViewer }) => {
|
test('should remove noscript when javaScriptEnabled is set to true', async ({ browser, server, showTraceViewer, browserType }) => {
|
||||||
const traceFile = test.info().outputPath('trace.pwtrace.zip');
|
const traceFile = test.info().outputPath('trace.zip');
|
||||||
const page = await browser.newPage({ javaScriptEnabled: true });
|
const page = await browser.newPage({ javaScriptEnabled: true });
|
||||||
await page.context().tracing.start({ snapshots: true, screenshots: true, sources: true });
|
await page.context().tracing.start({ snapshots: true, screenshots: true, sources: true });
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,9 @@ test('should collect trace with resources, but no js', async ({ context, page, s
|
||||||
await page.locator('input[type="file"]').setInputFiles(asset('file-to-upload.txt'));
|
await page.locator('input[type="file"]').setInputFiles(asset('file-to-upload.txt'));
|
||||||
await page.waitForTimeout(2000); // Give it some time to produce screenshots.
|
await page.waitForTimeout(2000); // Give it some time to produce screenshots.
|
||||||
await page.close();
|
await page.close();
|
||||||
await context.tracing.stop({ path: testInfo.outputPath('trace.pwtrace.zip') });
|
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
|
||||||
|
|
||||||
const { events, actions } = await parseTraceRaw(testInfo.outputPath('trace.pwtrace.zip'));
|
const { events, actions } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
|
||||||
expect(events[0].type).toBe('context-options');
|
expect(events[0].type).toBe('context-options');
|
||||||
expect(actions).toEqual([
|
expect(actions).toEqual([
|
||||||
'page.goto',
|
'page.goto',
|
||||||
|
|
@ -81,8 +81,8 @@ test('should use the correct apiName for event driven callbacks', async ({ conte
|
||||||
});
|
});
|
||||||
await page.evaluate(() => alert('yo'));
|
await page.evaluate(() => alert('yo'));
|
||||||
|
|
||||||
await context.tracing.stop({ path: testInfo.outputPath('trace.pwtrace.zip') });
|
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
|
||||||
const { events, actions } = await parseTraceRaw(testInfo.outputPath('trace.pwtrace.zip'));
|
const { events, actions } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
|
||||||
expect(events[0].type).toBe('context-options');
|
expect(events[0].type).toBe('context-options');
|
||||||
expect(actions).toEqual([
|
expect(actions).toEqual([
|
||||||
'page.route',
|
'page.route',
|
||||||
|
|
@ -102,9 +102,9 @@ test('should not collect snapshots by default', async ({ context, page, server }
|
||||||
await page.setContent('<button>Click</button>');
|
await page.setContent('<button>Click</button>');
|
||||||
await page.click('"Click"');
|
await page.click('"Click"');
|
||||||
await page.close();
|
await page.close();
|
||||||
await context.tracing.stop({ path: testInfo.outputPath('trace.pwtrace.zip') });
|
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
|
||||||
|
|
||||||
const { events } = await parseTraceRaw(testInfo.outputPath('trace.pwtrace.zip'));
|
const { events } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
|
||||||
expect(events.some(e => e.type === 'frame-snapshot')).toBeFalsy();
|
expect(events.some(e => e.type === 'frame-snapshot')).toBeFalsy();
|
||||||
expect(events.some(e => e.type === 'resource-snapshot')).toBeFalsy();
|
expect(events.some(e => e.type === 'resource-snapshot')).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
@ -113,8 +113,8 @@ test('should not include buffers in the trace', async ({ context, page, server }
|
||||||
await context.tracing.start({ snapshots: true });
|
await context.tracing.start({ snapshots: true });
|
||||||
await page.goto(server.PREFIX + '/empty.html');
|
await page.goto(server.PREFIX + '/empty.html');
|
||||||
await page.screenshot();
|
await page.screenshot();
|
||||||
await context.tracing.stop({ path: testInfo.outputPath('trace.pwtrace.zip') });
|
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
|
||||||
const { actionObjects } = await parseTraceRaw(testInfo.outputPath('trace.pwtrace.zip'));
|
const { actionObjects } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
|
||||||
const screenshotEvent = actionObjects.find(a => a.apiName === 'page.screenshot');
|
const screenshotEvent = actionObjects.find(a => a.apiName === 'page.screenshot');
|
||||||
expect(screenshotEvent.beforeSnapshot).toBeTruthy();
|
expect(screenshotEvent.beforeSnapshot).toBeTruthy();
|
||||||
expect(screenshotEvent.afterSnapshot).toBeTruthy();
|
expect(screenshotEvent.afterSnapshot).toBeTruthy();
|
||||||
|
|
@ -129,9 +129,9 @@ test('should exclude internal pages', async ({ browserName, context, page, serve
|
||||||
await context.tracing.start();
|
await context.tracing.start();
|
||||||
await context.storageState();
|
await context.storageState();
|
||||||
await page.close();
|
await page.close();
|
||||||
await context.tracing.stop({ path: testInfo.outputPath('trace.pwtrace.zip') });
|
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
|
||||||
|
|
||||||
const trace = await parseTraceRaw(testInfo.outputPath('trace.pwtrace.zip'));
|
const trace = await parseTraceRaw(testInfo.outputPath('trace.zip'));
|
||||||
const pageIds = new Set();
|
const pageIds = new Set();
|
||||||
trace.events.forEach(e => {
|
trace.events.forEach(e => {
|
||||||
const pageId = e.pageId;
|
const pageId = e.pageId;
|
||||||
|
|
@ -144,8 +144,8 @@ test('should exclude internal pages', async ({ browserName, context, page, serve
|
||||||
test('should include context API requests', async ({ browserName, context, page, server }, testInfo) => {
|
test('should include context API requests', async ({ browserName, context, page, server }, testInfo) => {
|
||||||
await context.tracing.start({ snapshots: true });
|
await context.tracing.start({ snapshots: true });
|
||||||
await page.request.post(server.PREFIX + '/simple.json', { data: { foo: 'bar' } });
|
await page.request.post(server.PREFIX + '/simple.json', { data: { foo: 'bar' } });
|
||||||
await context.tracing.stop({ path: testInfo.outputPath('trace.pwtrace.zip') });
|
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
|
||||||
const { events } = await parseTraceRaw(testInfo.outputPath('trace.pwtrace.zip'));
|
const { events } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
|
||||||
const postEvent = events.find(e => e.apiName === 'apiRequestContext.post');
|
const postEvent = events.find(e => e.apiName === 'apiRequestContext.post');
|
||||||
expect(postEvent).toBeTruthy();
|
expect(postEvent).toBeTruthy();
|
||||||
const harEntry = events.find(e => e.type === 'resource-snapshot');
|
const harEntry = events.find(e => e.type === 'resource-snapshot');
|
||||||
|
|
@ -428,9 +428,9 @@ for (const params of [
|
||||||
await page.setContent('<body style="box-sizing: border-box; width: 100%; height: 100%; margin:0; background: red; border: 50px solid blue"></body>');
|
await page.setContent('<body style="box-sizing: border-box; width: 100%; height: 100%; margin:0; background: red; border: 50px solid blue"></body>');
|
||||||
await page.evaluate(() => new Promise(window.builtinRequestAnimationFrame));
|
await page.evaluate(() => new Promise(window.builtinRequestAnimationFrame));
|
||||||
}
|
}
|
||||||
await context.tracing.stop({ path: testInfo.outputPath('trace.pwtrace.zip') });
|
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
|
||||||
|
|
||||||
const { events, resources } = await parseTraceRaw(testInfo.outputPath('trace.pwtrace.zip'));
|
const { events, resources } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
|
||||||
const frames = events.filter(e => e.type === 'screencast-frame');
|
const frames = events.filter(e => e.type === 'screencast-frame');
|
||||||
|
|
||||||
// Check all frame sizes.
|
// Check all frame sizes.
|
||||||
|
|
@ -460,10 +460,10 @@ test('should include interrupted actions', async ({ context, page, server }, tes
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent('<button>Click</button>');
|
await page.setContent('<button>Click</button>');
|
||||||
page.click('"ClickNoButton"').catch(() => {});
|
page.click('"ClickNoButton"').catch(() => {});
|
||||||
await context.tracing.stop({ path: testInfo.outputPath('trace.pwtrace.zip') });
|
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
|
||||||
await context.close();
|
await context.close();
|
||||||
|
|
||||||
const { events } = await parseTraceRaw(testInfo.outputPath('trace.pwtrace.zip'));
|
const { events } = await parseTraceRaw(testInfo.outputPath('trace.zip'));
|
||||||
const clickEvent = events.find(e => e.apiName === 'page.click');
|
const clickEvent = events.find(e => e.apiName === 'page.click');
|
||||||
expect(clickEvent).toBeTruthy();
|
expect(clickEvent).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
@ -475,7 +475,7 @@ test('should throw when starting with different options', async ({ context }) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should throw when stopping without start', async ({ context }, testInfo) => {
|
test('should throw when stopping without start', async ({ context }, testInfo) => {
|
||||||
const error = await context.tracing.stop({ path: testInfo.outputPath('trace.pwtrace.zip') }).catch(e => e);
|
const error = await context.tracing.stop({ path: testInfo.outputPath('trace.zip') }).catch(e => e);
|
||||||
expect(error.message).toContain('Must start tracing before stopping');
|
expect(error.message).toContain('Must start tracing before stopping');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -492,7 +492,7 @@ test('should work with multiple chunks', async ({ context, page, server }, testI
|
||||||
await page.click('"Click"');
|
await page.click('"Click"');
|
||||||
page.click('"ClickNoButton"', { timeout: 0 }).catch(() => {});
|
page.click('"ClickNoButton"', { timeout: 0 }).catch(() => {});
|
||||||
await page.evaluate(() => {});
|
await page.evaluate(() => {});
|
||||||
await context.tracing.stopChunk({ path: testInfo.outputPath('trace.pwtrace.zip') });
|
await context.tracing.stopChunk({ path: testInfo.outputPath('trace.zip') });
|
||||||
|
|
||||||
await context.tracing.startChunk();
|
await context.tracing.startChunk();
|
||||||
await page.hover('"Click"');
|
await page.hover('"Click"');
|
||||||
|
|
@ -502,7 +502,7 @@ test('should work with multiple chunks', async ({ context, page, server }, testI
|
||||||
await page.click('"Click"');
|
await page.click('"Click"');
|
||||||
await context.tracing.stopChunk(); // Should stop without a path.
|
await context.tracing.stopChunk(); // Should stop without a path.
|
||||||
|
|
||||||
const trace1 = await parseTraceRaw(testInfo.outputPath('trace.pwtrace.zip'));
|
const trace1 = await parseTraceRaw(testInfo.outputPath('trace.zip'));
|
||||||
expect(trace1.events[0].type).toBe('context-options');
|
expect(trace1.events[0].type).toBe('context-options');
|
||||||
expect(trace1.actions).toEqual([
|
expect(trace1.actions).toEqual([
|
||||||
'page.setContent',
|
'page.setContent',
|
||||||
|
|
@ -533,7 +533,7 @@ test('should export trace concurrently to second navigation', async ({ context,
|
||||||
await page.waitForTimeout(timeout);
|
await page.waitForTimeout(timeout);
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
promise,
|
promise,
|
||||||
context.tracing.stop({ path: testInfo.outputPath('trace.pwtrace.zip') }),
|
context.tracing.stop({ path: testInfo.outputPath('trace.zip') }),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -561,9 +561,9 @@ test('should ignore iframes in head', async ({ context, page, server }, testInfo
|
||||||
|
|
||||||
await context.tracing.start({ screenshots: true, snapshots: true });
|
await context.tracing.start({ screenshots: true, snapshots: true });
|
||||||
await page.click('button');
|
await page.click('button');
|
||||||
await context.tracing.stopChunk({ path: testInfo.outputPath('trace.pwtrace.zip') });
|
await context.tracing.stopChunk({ path: testInfo.outputPath('trace.zip') });
|
||||||
|
|
||||||
const trace = await parseTraceRaw(testInfo.outputPath('trace.pwtrace.zip'));
|
const trace = await parseTraceRaw(testInfo.outputPath('trace.zip'));
|
||||||
expect(trace.actions).toEqual([
|
expect(trace.actions).toEqual([
|
||||||
'page.click',
|
'page.click',
|
||||||
]);
|
]);
|
||||||
|
|
@ -581,7 +581,7 @@ test('should hide internal stack frames', async ({ context, page }, testInfo) =>
|
||||||
await page.setContent(`<div onclick='window.alert(123)'>Click me</div>`);
|
await page.setContent(`<div onclick='window.alert(123)'>Click me</div>`);
|
||||||
await page.click('div');
|
await page.click('div');
|
||||||
await evalPromise;
|
await evalPromise;
|
||||||
const tracePath = testInfo.outputPath('trace.pwtrace.zip');
|
const tracePath = testInfo.outputPath('trace.zip');
|
||||||
await context.tracing.stop({ path: tracePath });
|
await context.tracing.stop({ path: tracePath });
|
||||||
|
|
||||||
const trace = await parseTraceRaw(tracePath);
|
const trace = await parseTraceRaw(tracePath);
|
||||||
|
|
@ -602,7 +602,7 @@ test('should hide internal stack frames in expect', async ({ context, page }, te
|
||||||
await page.click('div');
|
await page.click('div');
|
||||||
await expect(page.locator('div')).toBeVisible();
|
await expect(page.locator('div')).toBeVisible();
|
||||||
await expectPromise;
|
await expectPromise;
|
||||||
const tracePath = testInfo.outputPath('trace.pwtrace.zip');
|
const tracePath = testInfo.outputPath('trace.zip');
|
||||||
await context.tracing.stop({ path: tracePath });
|
await context.tracing.stop({ path: tracePath });
|
||||||
|
|
||||||
const trace = await parseTraceRaw(tracePath);
|
const trace = await parseTraceRaw(tracePath);
|
||||||
|
|
@ -616,7 +616,7 @@ test('should record global request trace', async ({ request, context, server },
|
||||||
await (request as any)._tracing.start({ snapshots: true });
|
await (request as any)._tracing.start({ snapshots: true });
|
||||||
const url = server.PREFIX + '/simple.json';
|
const url = server.PREFIX + '/simple.json';
|
||||||
await request.get(url);
|
await request.get(url);
|
||||||
const tracePath = testInfo.outputPath('trace.pwtrace.zip');
|
const tracePath = testInfo.outputPath('trace.zip');
|
||||||
await (request as any)._tracing.stop({ path: tracePath });
|
await (request as any)._tracing.stop({ path: tracePath });
|
||||||
|
|
||||||
const trace = await parseTraceRaw(tracePath);
|
const trace = await parseTraceRaw(tracePath);
|
||||||
|
|
@ -649,7 +649,7 @@ test('should store global request traces separately', async ({ request, server,
|
||||||
request.get(url),
|
request.get(url),
|
||||||
request2.post(url)
|
request2.post(url)
|
||||||
]);
|
]);
|
||||||
const tracePath = testInfo.outputPath('trace.pwtrace.zip');
|
const tracePath = testInfo.outputPath('trace.zip');
|
||||||
const trace2Path = testInfo.outputPath('trace2.zip');
|
const trace2Path = testInfo.outputPath('trace2.zip');
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
(request as any)._tracing.stop({ path: tracePath }),
|
(request as any)._tracing.stop({ path: tracePath }),
|
||||||
|
|
@ -682,7 +682,7 @@ test('should store postData for global request', async ({ request, server }, tes
|
||||||
await request.post(url, {
|
await request.post(url, {
|
||||||
data: 'test'
|
data: 'test'
|
||||||
});
|
});
|
||||||
const tracePath = testInfo.outputPath('trace.pwtrace.zip');
|
const tracePath = testInfo.outputPath('trace.zip');
|
||||||
await (request as any)._tracing.stop({ path: tracePath });
|
await (request as any)._tracing.stop({ path: tracePath });
|
||||||
|
|
||||||
const trace = await parseTraceRaw(tracePath);
|
const trace = await parseTraceRaw(tracePath);
|
||||||
|
|
@ -755,7 +755,7 @@ test('should flush console events on tracing stop', async ({ context, page }, te
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
await promise;
|
await promise;
|
||||||
const tracePath = testInfo.outputPath('trace.pwtrace.zip');
|
const tracePath = testInfo.outputPath('trace.zip');
|
||||||
await context.tracing.stop({ path: tracePath });
|
await context.tracing.stop({ path: tracePath });
|
||||||
const trace = await parseTraceRaw(tracePath);
|
const trace = await parseTraceRaw(tracePath);
|
||||||
const events = trace.events.filter(e => e.type === 'console');
|
const events = trace.events.filter(e => e.type === 'console');
|
||||||
|
|
|
||||||
|
|
@ -799,7 +799,7 @@ it.describe('screencast', () => {
|
||||||
it.fixme(!headless || !!process.env.PLAYWRIGHT_CHROMIUM_USE_HEADLESS_NEW, 'different trace screencast image size on all browsers');
|
it.fixme(!headless || !!process.env.PLAYWRIGHT_CHROMIUM_USE_HEADLESS_NEW, 'different trace screencast image size on all browsers');
|
||||||
|
|
||||||
const size = { width: 500, height: 400 };
|
const size = { width: 500, height: 400 };
|
||||||
const traceFile = testInfo.outputPath('trace.pwtrace.zip');
|
const traceFile = testInfo.outputPath('trace.zip');
|
||||||
|
|
||||||
const context = await browser.newContext({
|
const context = await browser.newContext({
|
||||||
recordVideo: {
|
recordVideo: {
|
||||||
|
|
|
||||||
|
|
@ -235,25 +235,25 @@ test('should work with trace: on', async ({ runInlineTest }, testInfo) => {
|
||||||
expect(listFiles(testInfo.outputPath('test-results'))).toEqual([
|
expect(listFiles(testInfo.outputPath('test-results'))).toEqual([
|
||||||
'.last-run.json',
|
'.last-run.json',
|
||||||
'artifacts-failing',
|
'artifacts-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-own-context-failing',
|
'artifacts-own-context-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-own-context-passing',
|
'artifacts-own-context-passing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-passing',
|
'artifacts-passing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-persistent-failing',
|
'artifacts-persistent-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-persistent-passing',
|
'artifacts-persistent-passing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-shared-shared-failing',
|
'artifacts-shared-shared-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-shared-shared-passing',
|
'artifacts-shared-shared-passing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-two-contexts',
|
'artifacts-two-contexts',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-two-contexts-failing',
|
'artifacts-two-contexts-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -271,15 +271,15 @@ test('should work with trace: retain-on-failure', async ({ runInlineTest }, test
|
||||||
expect(listFiles(testInfo.outputPath('test-results'))).toEqual([
|
expect(listFiles(testInfo.outputPath('test-results'))).toEqual([
|
||||||
'.last-run.json',
|
'.last-run.json',
|
||||||
'artifacts-failing',
|
'artifacts-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-own-context-failing',
|
'artifacts-own-context-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-persistent-failing',
|
'artifacts-persistent-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-shared-shared-failing',
|
'artifacts-shared-shared-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-two-contexts-failing',
|
'artifacts-two-contexts-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -297,15 +297,15 @@ test('should work with trace: on-first-retry', async ({ runInlineTest }, testInf
|
||||||
expect(listFiles(testInfo.outputPath('test-results'))).toEqual([
|
expect(listFiles(testInfo.outputPath('test-results'))).toEqual([
|
||||||
'.last-run.json',
|
'.last-run.json',
|
||||||
'artifacts-failing-retry1',
|
'artifacts-failing-retry1',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-own-context-failing-retry1',
|
'artifacts-own-context-failing-retry1',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-persistent-failing-retry1',
|
'artifacts-persistent-failing-retry1',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-shared-shared-failing-retry1',
|
'artifacts-shared-shared-failing-retry1',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-two-contexts-failing-retry1',
|
'artifacts-two-contexts-failing-retry1',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -323,25 +323,25 @@ test('should work with trace: on-all-retries', async ({ runInlineTest }, testInf
|
||||||
expect(listFiles(testInfo.outputPath('test-results'))).toEqual([
|
expect(listFiles(testInfo.outputPath('test-results'))).toEqual([
|
||||||
'.last-run.json',
|
'.last-run.json',
|
||||||
'artifacts-failing-retry1',
|
'artifacts-failing-retry1',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-failing-retry2',
|
'artifacts-failing-retry2',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-own-context-failing-retry1',
|
'artifacts-own-context-failing-retry1',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-own-context-failing-retry2',
|
'artifacts-own-context-failing-retry2',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-persistent-failing-retry1',
|
'artifacts-persistent-failing-retry1',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-persistent-failing-retry2',
|
'artifacts-persistent-failing-retry2',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-shared-shared-failing-retry1',
|
'artifacts-shared-shared-failing-retry1',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-shared-shared-failing-retry2',
|
'artifacts-shared-shared-failing-retry2',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-two-contexts-failing-retry1',
|
'artifacts-two-contexts-failing-retry1',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-two-contexts-failing-retry2',
|
'artifacts-two-contexts-failing-retry2',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -359,15 +359,15 @@ test('should work with trace: retain-on-first-failure', async ({ runInlineTest }
|
||||||
expect(listFiles(testInfo.outputPath('test-results'))).toEqual([
|
expect(listFiles(testInfo.outputPath('test-results'))).toEqual([
|
||||||
'.last-run.json',
|
'.last-run.json',
|
||||||
'artifacts-failing',
|
'artifacts-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-own-context-failing',
|
'artifacts-own-context-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-persistent-failing',
|
'artifacts-persistent-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-shared-shared-failing',
|
'artifacts-shared-shared-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
'artifacts-two-contexts-failing',
|
'artifacts-two-contexts-failing',
|
||||||
' trace.pwtrace.zip',
|
' trace.zip',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ test('should reuse context with trace if mode=when-possible', async ({ runInline
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(2);
|
expect(result.passed).toBe(2);
|
||||||
|
|
||||||
const trace1 = await parseTrace(testInfo.outputPath('test-results', 'reuse-one', 'trace.pwtrace.zip'));
|
const trace1 = await parseTrace(testInfo.outputPath('test-results', 'reuse-one', 'trace.zip'));
|
||||||
expect(trace1.actionTree).toEqual([
|
expect(trace1.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
' fixture: browser',
|
' fixture: browser',
|
||||||
|
|
@ -131,7 +131,7 @@ test('should reuse context with trace if mode=when-possible', async ({ runInline
|
||||||
expect(trace1.traceModel.storage().snapshotsForTest().length).toBeGreaterThan(0);
|
expect(trace1.traceModel.storage().snapshotsForTest().length).toBeGreaterThan(0);
|
||||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'reuse-one', 'trace-1.zip'))).toBe(false);
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'reuse-one', 'trace-1.zip'))).toBe(false);
|
||||||
|
|
||||||
const trace2 = await parseTrace(testInfo.outputPath('test-results', 'reuse-two', 'trace.pwtrace.zip'));
|
const trace2 = await parseTrace(testInfo.outputPath('test-results', 'reuse-two', 'trace.zip'));
|
||||||
expect(trace2.actionTree).toEqual([
|
expect(trace2.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
' fixture: context',
|
' fixture: context',
|
||||||
|
|
@ -533,6 +533,6 @@ test('should survive serial mode with tracing and reuse', async ({ runInlineTest
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(2);
|
expect(result.passed).toBe(2);
|
||||||
|
|
||||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'reuse-one', 'trace.pwtrace.zip'))).toBe(true);
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'reuse-one', 'trace.zip'))).toBe(true);
|
||||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'reuse-two', 'trace.pwtrace.zip'))).toBe(true);
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'reuse-two', 'trace.zip'))).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ test('should stop tracing with trace: on-first-retry, when not retrying', async
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
expect(result.flaky).toBe(1);
|
expect(result.flaky).toBe(1);
|
||||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-shared-flaky-retry1', 'trace.pwtrace.zip'))).toBeTruthy();
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-shared-flaky-retry1', 'trace.zip'))).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should record api trace', async ({ runInlineTest, server }, testInfo) => {
|
test('should record api trace', async ({ runInlineTest, server }, testInfo) => {
|
||||||
|
|
@ -86,7 +86,7 @@ test('should record api trace', async ({ runInlineTest, server }, testInfo) => {
|
||||||
expect(result.passed).toBe(2);
|
expect(result.passed).toBe(2);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
// One trace file for request context and one for each APIRequestContext
|
// One trace file for request context and one for each APIRequestContext
|
||||||
const trace1 = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.pwtrace.zip'));
|
const trace1 = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip'));
|
||||||
expect(trace1.actionTree).toEqual([
|
expect(trace1.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
' fixture: request',
|
' fixture: request',
|
||||||
|
|
@ -105,14 +105,14 @@ test('should record api trace', async ({ runInlineTest, server }, testInfo) => {
|
||||||
' fixture: request',
|
' fixture: request',
|
||||||
' apiRequestContext.dispose',
|
' apiRequestContext.dispose',
|
||||||
]);
|
]);
|
||||||
const trace2 = await parseTrace(testInfo.outputPath('test-results', 'a-api-pass', 'trace.pwtrace.zip'));
|
const trace2 = await parseTrace(testInfo.outputPath('test-results', 'a-api-pass', 'trace.zip'));
|
||||||
expect(trace2.actionTree).toEqual([
|
expect(trace2.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
'apiRequest.newContext',
|
'apiRequest.newContext',
|
||||||
'apiRequestContext.get',
|
'apiRequestContext.get',
|
||||||
'After Hooks',
|
'After Hooks',
|
||||||
]);
|
]);
|
||||||
const trace3 = await parseTrace(testInfo.outputPath('test-results', 'a-fail', 'trace.pwtrace.zip'));
|
const trace3 = await parseTrace(testInfo.outputPath('test-results', 'a-fail', 'trace.zip'));
|
||||||
expect(trace3.actionTree).toEqual([
|
expect(trace3.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
' fixture: request',
|
' fixture: request',
|
||||||
|
|
@ -204,7 +204,7 @@ test('should not mixup network files between contexts', async ({ runInlineTest,
|
||||||
}, { workers: 1, timeout: 15000 });
|
}, { workers: 1, timeout: 15000 });
|
||||||
expect(result.exitCode).toEqual(0);
|
expect(result.exitCode).toEqual(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-example', 'trace.pwtrace.zip'))).toBe(true);
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-example', 'trace.zip'))).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should save sources when requested', async ({ runInlineTest }, testInfo) => {
|
test('should save sources when requested', async ({ runInlineTest }, testInfo) => {
|
||||||
|
|
@ -224,7 +224,7 @@ test('should save sources when requested', async ({ runInlineTest }, testInfo) =
|
||||||
`,
|
`,
|
||||||
}, { workers: 1 });
|
}, { workers: 1 });
|
||||||
expect(result.exitCode).toEqual(0);
|
expect(result.exitCode).toEqual(0);
|
||||||
const { resources } = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.pwtrace.zip'));
|
const { resources } = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip'));
|
||||||
expect([...resources.keys()].filter(name => name.startsWith('resources/src@'))).toHaveLength(1);
|
expect([...resources.keys()].filter(name => name.startsWith('resources/src@'))).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -248,7 +248,7 @@ test('should not save sources when not requested', async ({ runInlineTest }, tes
|
||||||
`,
|
`,
|
||||||
}, { workers: 1 });
|
}, { workers: 1 });
|
||||||
expect(result.exitCode).toEqual(0);
|
expect(result.exitCode).toEqual(0);
|
||||||
const { resources } = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.pwtrace.zip'));
|
const { resources } = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip'));
|
||||||
expect([...resources.keys()].filter(name => name.startsWith('resources/src@'))).toHaveLength(0);
|
expect([...resources.keys()].filter(name => name.startsWith('resources/src@'))).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -283,8 +283,8 @@ test('should work in serial mode', async ({ runInlineTest }, testInfo) => {
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-serial-passes', 'trace.pwtrace.zip'))).toBeFalsy();
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-serial-passes', 'trace.zip'))).toBeFalsy();
|
||||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-serial-fails', 'trace.pwtrace.zip'))).toBeTruthy();
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-serial-fails', 'trace.zip'))).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not override trace file in afterAll', async ({ runInlineTest, server }, testInfo) => {
|
test('should not override trace file in afterAll', async ({ runInlineTest, server }, testInfo) => {
|
||||||
|
|
@ -313,7 +313,7 @@ test('should not override trace file in afterAll', async ({ runInlineTest, serve
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
const trace1 = await parseTrace(testInfo.outputPath('test-results', 'a-test-1', 'trace.pwtrace.zip'));
|
const trace1 = await parseTrace(testInfo.outputPath('test-results', 'a-test-1', 'trace.zip'));
|
||||||
|
|
||||||
expect(trace1.actionTree).toEqual([
|
expect(trace1.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
|
|
@ -338,7 +338,7 @@ test('should not override trace file in afterAll', async ({ runInlineTest, serve
|
||||||
]);
|
]);
|
||||||
expect(trace1.errors).toEqual([`'oh no!'`]);
|
expect(trace1.errors).toEqual([`'oh no!'`]);
|
||||||
|
|
||||||
const error = await parseTrace(testInfo.outputPath('test-results', 'a-test-2', 'trace.pwtrace.zip')).catch(e => e);
|
const error = await parseTrace(testInfo.outputPath('test-results', 'a-test-2', 'trace.zip')).catch(e => e);
|
||||||
expect(error).toBeTruthy();
|
expect(error).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -366,8 +366,8 @@ test('should retain traces for interrupted tests', async ({ runInlineTest }, tes
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(result.interrupted).toBe(1);
|
expect(result.interrupted).toBe(1);
|
||||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-test-1', 'trace.pwtrace.zip'))).toBeTruthy();
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-test-1', 'trace.zip'))).toBeTruthy();
|
||||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'b-test-2', 'trace.pwtrace.zip'))).toBeTruthy();
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'b-test-2', 'trace.zip'))).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should respect --trace', async ({ runInlineTest }, testInfo) => {
|
test('should respect --trace', async ({ runInlineTest }, testInfo) => {
|
||||||
|
|
@ -382,7 +382,7 @@ test('should respect --trace', async ({ runInlineTest }, testInfo) => {
|
||||||
|
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-test-1', 'trace.pwtrace.zip'))).toBeTruthy();
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-test-1', 'trace.zip'))).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should respect PW_TEST_DISABLE_TRACING', async ({ runInlineTest }, testInfo) => {
|
test('should respect PW_TEST_DISABLE_TRACING', async ({ runInlineTest }, testInfo) => {
|
||||||
|
|
@ -400,7 +400,7 @@ test('should respect PW_TEST_DISABLE_TRACING', async ({ runInlineTest }, testInf
|
||||||
|
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-test-1', 'trace.pwtrace.zip'))).toBe(false);
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-test-1', 'trace.zip'))).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const mode of ['off', 'retain-on-failure', 'on-first-retry', 'on-all-retries', 'retain-on-first-failure']) {
|
for (const mode of ['off', 'retain-on-failure', 'on-first-retry', 'on-all-retries', 'retain-on-first-failure']) {
|
||||||
|
|
@ -465,7 +465,7 @@ test(`trace:retain-on-failure should create trace if context is closed before fa
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
}, { trace: 'retain-on-failure' });
|
}, { trace: 'retain-on-failure' });
|
||||||
const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.pwtrace.zip');
|
const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.zip');
|
||||||
const trace = await parseTrace(tracePath);
|
const trace = await parseTrace(tracePath);
|
||||||
expect(trace.apiNames).toContain('page.goto');
|
expect(trace.apiNames).toContain('page.goto');
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
|
|
@ -487,7 +487,7 @@ test(`trace:retain-on-failure should create trace if context is closed before fa
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
}, { trace: 'retain-on-failure' });
|
}, { trace: 'retain-on-failure' });
|
||||||
const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.pwtrace.zip');
|
const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.zip');
|
||||||
const trace = await parseTrace(tracePath);
|
const trace = await parseTrace(tracePath);
|
||||||
expect(trace.apiNames).toContain('page.goto');
|
expect(trace.apiNames).toContain('page.goto');
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
|
|
@ -507,7 +507,7 @@ test(`trace:retain-on-failure should create trace if request context is disposed
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
}, { trace: 'retain-on-failure' });
|
}, { trace: 'retain-on-failure' });
|
||||||
const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.pwtrace.zip');
|
const tracePath = test.info().outputPath('test-results', 'a-passing-test', 'trace.zip');
|
||||||
const trace = await parseTrace(tracePath);
|
const trace = await parseTrace(tracePath);
|
||||||
expect(trace.apiNames).toContain('apiRequestContext.get');
|
expect(trace.apiNames).toContain('apiRequestContext.get');
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
|
|
@ -529,7 +529,7 @@ test('should include attachments by default', async ({ runInlineTest, server },
|
||||||
|
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.pwtrace.zip'));
|
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip'));
|
||||||
expect(trace.apiNames).toEqual([
|
expect(trace.apiNames).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
`attach "foo"`,
|
`attach "foo"`,
|
||||||
|
|
@ -559,7 +559,7 @@ test('should opt out of attachments', async ({ runInlineTest, server }, testInfo
|
||||||
|
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.pwtrace.zip'));
|
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip'));
|
||||||
expect(trace.apiNames).toEqual([
|
expect(trace.apiNames).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
`attach "foo"`,
|
`attach "foo"`,
|
||||||
|
|
@ -592,7 +592,7 @@ test('should record with custom page fixture', async ({ runInlineTest }, testInf
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
expect(result.output).toContain('failure!');
|
expect(result.output).toContain('failure!');
|
||||||
const trace = await parseTraceRaw(testInfo.outputPath('test-results', 'a-fails', 'trace.pwtrace.zip'));
|
const trace = await parseTraceRaw(testInfo.outputPath('test-results', 'a-fails', 'trace.zip'));
|
||||||
expect(trace.events).toContainEqual(expect.objectContaining({
|
expect(trace.events).toContainEqual(expect.objectContaining({
|
||||||
type: 'frame-snapshot',
|
type: 'frame-snapshot',
|
||||||
}));
|
}));
|
||||||
|
|
@ -617,7 +617,7 @@ test('should expand expect.toPass', async ({ runInlineTest }, testInfo) => {
|
||||||
|
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.pwtrace.zip'));
|
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip'));
|
||||||
expect(trace.actionTree).toEqual([
|
expect(trace.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
' fixture: browser',
|
' fixture: browser',
|
||||||
|
|
@ -656,7 +656,7 @@ test('should show non-expect error in trace', async ({ runInlineTest }, testInfo
|
||||||
|
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-fail', 'trace.pwtrace.zip'));
|
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-fail', 'trace.zip'));
|
||||||
expect(trace.actionTree).toEqual([
|
expect(trace.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
' fixture: browser',
|
' fixture: browser',
|
||||||
|
|
@ -692,7 +692,7 @@ test('should show error from beforeAll in trace', async ({ runInlineTest }, test
|
||||||
|
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-fail', 'trace.pwtrace.zip'));
|
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-fail', 'trace.zip'));
|
||||||
expect(trace.errors).toEqual(['Error: Oh my!']);
|
expect(trace.errors).toEqual(['Error: Oh my!']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -730,7 +730,7 @@ test('should not throw when attachment is missing', async ({ runInlineTest }, te
|
||||||
|
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-passes', 'trace.pwtrace.zip'));
|
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-passes', 'trace.zip'));
|
||||||
expect(trace.actionTree).toContain('attach "screenshot"');
|
expect(trace.actionTree).toContain('attach "screenshot"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -754,7 +754,7 @@ test('should not throw when screenshot on failure fails', async ({ runInlineTest
|
||||||
|
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-has-pdf-page', 'trace.pwtrace.zip'));
|
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-has-pdf-page', 'trace.zip'));
|
||||||
const attachedScreenshots = trace.actionTree.filter(s => s.trim() === `attach "screenshot"`);
|
const attachedScreenshots = trace.actionTree.filter(s => s.trim() === `attach "screenshot"`);
|
||||||
// One screenshot for the page, no screenshot for pdf page since it should have failed.
|
// One screenshot for the page, no screenshot for pdf page since it should have failed.
|
||||||
expect(attachedScreenshots.length).toBe(1);
|
expect(attachedScreenshots.length).toBe(1);
|
||||||
|
|
@ -778,7 +778,7 @@ test('should use custom expect message in trace', async ({ runInlineTest }, test
|
||||||
|
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-fail', 'trace.pwtrace.zip'));
|
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-fail', 'trace.zip'));
|
||||||
expect(trace.actionTree).toEqual([
|
expect(trace.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
' fixture: browser',
|
' fixture: browser',
|
||||||
|
|
@ -837,7 +837,7 @@ test('should not throw when merging traces multiple times', async ({ runInlineTe
|
||||||
|
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-foo', 'trace.pwtrace.zip'))).toBe(true);
|
expect(fs.existsSync(testInfo.outputPath('test-results', 'a-foo', 'trace.zip'))).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should record nested steps, even after timeout', async ({ runInlineTest }, testInfo) => {
|
test('should record nested steps, even after timeout', async ({ runInlineTest }, testInfo) => {
|
||||||
|
|
@ -928,7 +928,7 @@ test('should record nested steps, even after timeout', async ({ runInlineTest },
|
||||||
|
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-example', 'trace.pwtrace.zip'));
|
const trace = await parseTrace(testInfo.outputPath('test-results', 'a-example', 'trace.zip'));
|
||||||
expect(trace.actionTree).toEqual([
|
expect(trace.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
' beforeAll hook',
|
' beforeAll hook',
|
||||||
|
|
@ -1022,14 +1022,14 @@ test('should attribute worker fixture teardown to the right test', async ({ runI
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
const trace1 = await parseTrace(testInfo.outputPath('test-results', 'a-one', 'trace.pwtrace.zip'));
|
const trace1 = await parseTrace(testInfo.outputPath('test-results', 'a-one', 'trace.zip'));
|
||||||
expect(trace1.actionTree).toEqual([
|
expect(trace1.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
' fixture: foo',
|
' fixture: foo',
|
||||||
' step in foo setup',
|
' step in foo setup',
|
||||||
'After Hooks',
|
'After Hooks',
|
||||||
]);
|
]);
|
||||||
const trace2 = await parseTrace(testInfo.outputPath('test-results', 'a-two', 'trace.pwtrace.zip'));
|
const trace2 = await parseTrace(testInfo.outputPath('test-results', 'a-two', 'trace.zip'));
|
||||||
expect(trace2.actionTree).toEqual([
|
expect(trace2.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
'After Hooks',
|
'After Hooks',
|
||||||
|
|
@ -1050,11 +1050,11 @@ test('trace:retain-on-first-failure should create trace but only on first failur
|
||||||
`,
|
`,
|
||||||
}, { trace: 'retain-on-first-failure', retries: 1 });
|
}, { trace: 'retain-on-first-failure', retries: 1 });
|
||||||
|
|
||||||
const retryTracePath = test.info().outputPath('test-results', 'a-fail-retry1', 'trace.pwtrace.zip');
|
const retryTracePath = test.info().outputPath('test-results', 'a-fail-retry1', 'trace.zip');
|
||||||
const retryTraceExists = fs.existsSync(retryTracePath);
|
const retryTraceExists = fs.existsSync(retryTracePath);
|
||||||
expect(retryTraceExists).toBe(false);
|
expect(retryTraceExists).toBe(false);
|
||||||
|
|
||||||
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.pwtrace.zip');
|
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip');
|
||||||
const trace = await parseTrace(tracePath);
|
const trace = await parseTrace(tracePath);
|
||||||
expect(trace.apiNames).toContain('page.goto');
|
expect(trace.apiNames).toContain('page.goto');
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
|
|
@ -1071,7 +1071,7 @@ test('trace:retain-on-first-failure should create trace if context is closed bef
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
}, { trace: 'retain-on-first-failure' });
|
}, { trace: 'retain-on-first-failure' });
|
||||||
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.pwtrace.zip');
|
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip');
|
||||||
const trace = await parseTrace(tracePath);
|
const trace = await parseTrace(tracePath);
|
||||||
expect(trace.apiNames).toContain('page.goto');
|
expect(trace.apiNames).toContain('page.goto');
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
|
|
@ -1090,7 +1090,7 @@ test('trace:retain-on-first-failure should create trace if context is closed bef
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
}, { trace: 'retain-on-first-failure' });
|
}, { trace: 'retain-on-first-failure' });
|
||||||
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.pwtrace.zip');
|
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip');
|
||||||
const trace = await parseTrace(tracePath);
|
const trace = await parseTrace(tracePath);
|
||||||
expect(trace.apiNames).toContain('page.goto');
|
expect(trace.apiNames).toContain('page.goto');
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
|
|
@ -1107,7 +1107,7 @@ test('trace:retain-on-first-failure should create trace if request context is di
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
}, { trace: 'retain-on-first-failure' });
|
}, { trace: 'retain-on-first-failure' });
|
||||||
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.pwtrace.zip');
|
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip');
|
||||||
const trace = await parseTrace(tracePath);
|
const trace = await parseTrace(tracePath);
|
||||||
expect(trace.apiNames).toContain('apiRequestContext.get');
|
expect(trace.apiNames).toContain('apiRequestContext.get');
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
|
|
@ -1132,7 +1132,7 @@ test('should not corrupt actions when no library trace is present', async ({ run
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
|
|
||||||
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.pwtrace.zip');
|
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip');
|
||||||
const trace = await parseTrace(tracePath);
|
const trace = await parseTrace(tracePath);
|
||||||
expect(trace.actionTree).toEqual([
|
expect(trace.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
|
|
@ -1162,7 +1162,7 @@ test('should record trace for manually created context in a failed test', async
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
|
|
||||||
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.pwtrace.zip');
|
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip');
|
||||||
const trace = await parseTrace(tracePath);
|
const trace = await parseTrace(tracePath);
|
||||||
expect(trace.actionTree).toEqual([
|
expect(trace.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
|
|
@ -1204,7 +1204,7 @@ test('should not nest top level expect into unfinished api calls ', {
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.failed).toBe(0);
|
expect(result.failed).toBe(0);
|
||||||
|
|
||||||
const tracePath = test.info().outputPath('test-results', 'a-pass', 'trace.pwtrace.zip');
|
const tracePath = test.info().outputPath('test-results', 'a-pass', 'trace.zip');
|
||||||
const trace = await parseTrace(tracePath);
|
const trace = await parseTrace(tracePath);
|
||||||
expect(trace.actionTree).toEqual([
|
expect(trace.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
|
|
@ -1246,7 +1246,7 @@ test('should record trace after fixture teardown timeout', {
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
|
|
||||||
const tracePath = test.info().outputPath('test-results', 'a-fails', 'trace.pwtrace.zip');
|
const tracePath = test.info().outputPath('test-results', 'a-fails', 'trace.zip');
|
||||||
const trace = await parseTrace(tracePath);
|
const trace = await parseTrace(tracePath);
|
||||||
expect(trace.actionTree).toEqual([
|
expect(trace.actionTree).toEqual([
|
||||||
'Before Hooks',
|
'Before Hooks',
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ test('render trace attachment', async ({ runInlineTest }) => {
|
||||||
test('one', async ({}, testInfo) => {
|
test('one', async ({}, testInfo) => {
|
||||||
testInfo.attachments.push({
|
testInfo.attachments.push({
|
||||||
name: 'trace',
|
name: 'trace',
|
||||||
path: testInfo.outputPath('my dir with space', 'trace.pwtrace.zip'),
|
path: testInfo.outputPath('my dir with space', 'trace.zip'),
|
||||||
contentType: 'application/zip'
|
contentType: 'application/zip'
|
||||||
});
|
});
|
||||||
expect(1).toBe(0);
|
expect(1).toBe(0);
|
||||||
|
|
@ -75,8 +75,8 @@ test('render trace attachment', async ({ runInlineTest }) => {
|
||||||
}, { reporter: 'line' });
|
}, { reporter: 'line' });
|
||||||
const text = result.output.replace(/\\/g, '/');
|
const text = result.output.replace(/\\/g, '/');
|
||||||
expect(text).toContain(' attachment #1: trace (application/zip) ─────────────────────────────────────────────────────────');
|
expect(text).toContain(' attachment #1: trace (application/zip) ─────────────────────────────────────────────────────────');
|
||||||
expect(text).toContain(' test-results/a-one/my dir with space/trace.pwtrace.zip');
|
expect(text).toContain(' test-results/a-one/my dir with space/trace.zip');
|
||||||
expect(text).toContain('npx playwright show-trace "test-results/a-one/my dir with space/trace.pwtrace.zip"');
|
expect(text).toContain('npx playwright show-trace "test-results/a-one/my dir with space/trace.zip"');
|
||||||
expect(text).toContain(' ────────────────────────────────────────────────────────────────────────────────────────────────');
|
expect(text).toContain(' ────────────────────────────────────────────────────────────────────────────────────────────────');
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1246,3 +1246,42 @@ fixture | fixture: page
|
||||||
fixture | fixture: context
|
fixture | fixture: context
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('test location to test.step', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'reporter.ts': stepIndentReporter,
|
||||||
|
'helper.ts': `
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
|
||||||
|
export async function dummyStep(test, title, action, location) {
|
||||||
|
return await test.step(title, action, { location });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCustomLocation() {
|
||||||
|
return { file: 'dummy-file.ts', line: 123, column: 45 };
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'playwright.config.ts': `
|
||||||
|
module.exports = {
|
||||||
|
reporter: './reporter',
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
import { dummyStep, getCustomLocation } from './helper';
|
||||||
|
|
||||||
|
test('custom location test', async () => {
|
||||||
|
const location = getCustomLocation();
|
||||||
|
await dummyStep(test, 'Perform a dummy step', async () => {
|
||||||
|
}, location);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
}, { reporter: '', workers: 1 });
|
||||||
|
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(stripAnsi(result.output)).toBe(`
|
||||||
|
hook |Before Hooks
|
||||||
|
test.step |Perform a dummy step @ dummy-file.ts:123
|
||||||
|
hook |After Hooks
|
||||||
|
`);
|
||||||
|
});
|
||||||
2
utils/generate_types/overrides-test.d.ts
vendored
2
utils/generate_types/overrides-test.d.ts
vendored
|
|
@ -128,7 +128,7 @@ export interface TestType<TestArgs extends KeyValue, WorkerArgs extends KeyValue
|
||||||
afterAll(inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): void;
|
afterAll(inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): void;
|
||||||
afterAll(title: string, inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): void;
|
afterAll(title: string, inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise<any> | any): void;
|
||||||
use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void;
|
use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void;
|
||||||
step<T>(title: string, body: () => T | Promise<T>, options?: { box?: boolean }): Promise<T>;
|
step<T>(title: string, body: () => T | Promise<T>, options?: { box?: boolean, location?: Location }): Promise<T>;
|
||||||
expect: Expect<{}>;
|
expect: Expect<{}>;
|
||||||
extend<T extends KeyValue, W extends KeyValue = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
|
extend<T extends KeyValue, W extends KeyValue = {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
|
||||||
info(): TestInfo;
|
info(): TestInfo;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue