Compare commits
25 commits
main
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e130fa8ed | ||
|
|
2f17a270b6 | ||
|
|
b129abab04 | ||
|
|
d8a5f3b331 | ||
|
|
51fa0fffbd | ||
|
|
80f44f3c16 | ||
|
|
4bd2d2502e | ||
|
|
e8989f83d9 | ||
|
|
1d94caa103 | ||
|
|
5be51684fb | ||
|
|
0fe7a102f3 | ||
|
|
4ec4ccf7f3 | ||
|
|
3b4d32e731 | ||
|
|
4ccaef69be | ||
|
|
4f3f6eecae | ||
|
|
d557b7b256 | ||
|
|
1368bca737 | ||
|
|
6c3fc49cf3 | ||
|
|
4ae151f42a | ||
|
|
a11585f0d6 | ||
|
|
afcf8d296c | ||
|
|
599d074f71 | ||
|
|
245179103e | ||
|
|
56ca1e13e1 | ||
|
|
816c003189 |
|
|
@ -1,6 +1,6 @@
|
|||
# 🎭 Playwright
|
||||
|
||||
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop -->
|
||||
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop -->
|
||||
|
||||
## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr
|
|||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Chromium <!-- GEN:chromium-version -->127.0.6533.5<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Chromium <!-- GEN:chromium-version -->127.0.6533.17<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Firefox <!-- GEN:firefox-version -->127.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
|
|
|
|||
|
|
@ -748,83 +748,6 @@ await page.SetContentAsync("<script>\n" +
|
|||
await page.GetByRole(AriaRole.Button).ClickAsync();
|
||||
```
|
||||
|
||||
An example of passing an element handle:
|
||||
|
||||
```js
|
||||
await context.exposeBinding('clicked', async (source, element) => {
|
||||
console.log(await element.textContent());
|
||||
}, { handle: true });
|
||||
await page.setContent(`
|
||||
<script>
|
||||
document.addEventListener('click', event => window.clicked(event.target));
|
||||
</script>
|
||||
<div>Click me</div>
|
||||
<div>Or click me</div>
|
||||
`);
|
||||
```
|
||||
|
||||
```java
|
||||
context.exposeBinding("clicked", (source, args) -> {
|
||||
ElementHandle element = (ElementHandle) args[0];
|
||||
System.out.println(element.textContent());
|
||||
return null;
|
||||
}, new BrowserContext.ExposeBindingOptions().setHandle(true));
|
||||
page.setContent("" +
|
||||
"<script>\n" +
|
||||
" document.addEventListener('click', event => window.clicked(event.target));\n" +
|
||||
"</script>\n" +
|
||||
"<div>Click me</div>\n" +
|
||||
"<div>Or click me</div>\n");
|
||||
```
|
||||
|
||||
```python async
|
||||
async def print(source, element):
|
||||
print(await element.text_content())
|
||||
|
||||
await context.expose_binding("clicked", print, handle=true)
|
||||
await page.set_content("""
|
||||
<script>
|
||||
document.addEventListener('click', event => window.clicked(event.target));
|
||||
</script>
|
||||
<div>Click me</div>
|
||||
<div>Or click me</div>
|
||||
""")
|
||||
```
|
||||
|
||||
```python sync
|
||||
def print(source, element):
|
||||
print(element.text_content())
|
||||
|
||||
context.expose_binding("clicked", print, handle=true)
|
||||
page.set_content("""
|
||||
<script>
|
||||
document.addEventListener('click', event => window.clicked(event.target));
|
||||
</script>
|
||||
<div>Click me</div>
|
||||
<div>Or click me</div>
|
||||
""")
|
||||
```
|
||||
|
||||
```csharp
|
||||
var result = new TaskCompletionSource<string>();
|
||||
var page = await Context.NewPageAsync();
|
||||
await Context.ExposeBindingAsync("clicked", async (BindingSource _, IJSHandle t) =>
|
||||
{
|
||||
return result.TrySetResult(await t.AsElement().TextContentAsync());
|
||||
});
|
||||
|
||||
await page.SetContentAsync("<script>\n" +
|
||||
" document.addEventListener('click', event => window.clicked(event.target));\n" +
|
||||
"</script>\n" +
|
||||
"<div>Click me</div>\n" +
|
||||
"<div>Or click me</div>\n");
|
||||
|
||||
await page.ClickAsync("div");
|
||||
// Note: it makes sense to await the result here, because otherwise, the context
|
||||
// gets closed and the binding function will throw an exception.
|
||||
Assert.AreEqual("Click me", await result.Task);
|
||||
```
|
||||
|
||||
### param: BrowserContext.exposeBinding.name
|
||||
* since: v1.8
|
||||
- `name` <[string]>
|
||||
|
|
@ -839,6 +762,7 @@ Callback function that will be called in the Playwright's context.
|
|||
|
||||
### option: BrowserContext.exposeBinding.handle
|
||||
* since: v1.8
|
||||
* deprecated: This option will be removed in the future.
|
||||
- `handle` <[boolean]>
|
||||
|
||||
Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ await page.Clock.FastForwardAsync("30:00");
|
|||
|
||||
### param: Clock.fastForward.ticks
|
||||
* since: v1.45
|
||||
- `ticks` <[int]|[string]>
|
||||
- `ticks` <[long]|[string]>
|
||||
|
||||
Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
|
||||
|
||||
|
|
@ -64,8 +64,23 @@ Install fake implementations for the following time-related functions:
|
|||
Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and control the behavior of time-dependent functions. See [`method: Clock.runFor`] and [`method: Clock.fastForward`] for more information.
|
||||
|
||||
### option: Clock.install.time
|
||||
* langs: js, java
|
||||
* since: v1.45
|
||||
- `time` <[int]|[string]|[Date]>
|
||||
- `time` <[long]|[string]|[Date]>
|
||||
|
||||
Time to initialize with, current system time by default.
|
||||
|
||||
### option: Clock.install.time
|
||||
* langs: python
|
||||
* since: v1.45
|
||||
- `time` <[float]|[string]|[Date]>
|
||||
|
||||
Time to initialize with, current system time by default.
|
||||
|
||||
### option: Clock.install.time
|
||||
* langs: csharp
|
||||
* since: v1.45
|
||||
- `time` <[string]|[Date]>
|
||||
|
||||
Time to initialize with, current system time by default.
|
||||
|
||||
|
|
@ -103,7 +118,7 @@ await page.Clock.RunForAsync("30:00");
|
|||
|
||||
### param: Clock.runFor.ticks
|
||||
* since: v1.45
|
||||
- `ticks` <[int]|[string]>
|
||||
- `ticks` <[long]|[string]>
|
||||
|
||||
Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
|
||||
|
||||
|
|
@ -136,7 +151,8 @@ page.clock.pause_at("2020-02-02")
|
|||
```
|
||||
|
||||
```java
|
||||
page.clock().pauseAt(Instant.parse("2020-02-02"));
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd");
|
||||
page.clock().pauseAt(format.parse("2020-02-02"));
|
||||
page.clock().pauseAt("2020-02-02");
|
||||
```
|
||||
|
||||
|
|
@ -146,9 +162,25 @@ await page.Clock.PauseAtAsync("2020-02-02");
|
|||
```
|
||||
|
||||
### param: Clock.pauseAt.time
|
||||
* langs: js, java
|
||||
* since: v1.45
|
||||
- `time` <[int]|[string]|[Date]>
|
||||
- `time` <[long]|[string]|[Date]>
|
||||
|
||||
Time to pause at.
|
||||
|
||||
### param: Clock.pauseAt.time
|
||||
* langs: python
|
||||
* since: v1.45
|
||||
- `time` <[float]|[string]|[Date]>
|
||||
|
||||
Time to pause at.
|
||||
|
||||
### param: Clock.pauseAt.time
|
||||
* langs: csharp
|
||||
* since: v1.45
|
||||
- `time` <[Date]|[string]>
|
||||
|
||||
Time to pause at.
|
||||
|
||||
## async method: Clock.resume
|
||||
* since: v1.45
|
||||
|
|
@ -182,8 +214,8 @@ page.clock.set_fixed_time("2020-02-02")
|
|||
```
|
||||
|
||||
```java
|
||||
page.clock().setFixedTime(Instant.now());
|
||||
page.clock().setFixedTime(Instant.parse("2020-02-02"));
|
||||
page.clock().setFixedTime(new Date());
|
||||
page.clock().setFixedTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
|
||||
page.clock().setFixedTime("2020-02-02");
|
||||
```
|
||||
|
||||
|
|
@ -194,8 +226,23 @@ await page.Clock.SetFixedTimeAsync("2020-02-02");
|
|||
```
|
||||
|
||||
### param: Clock.setFixedTime.time
|
||||
* langs: js, java
|
||||
* since: v1.45
|
||||
- `time` <[int]|[string]|[Date]>
|
||||
- `time` <[long]|[string]|[Date]>
|
||||
|
||||
Time to be set in milliseconds.
|
||||
|
||||
### param: Clock.setFixedTime.time
|
||||
* langs: python
|
||||
* since: v1.45
|
||||
- `time` <[float]|[string]|[Date]>
|
||||
|
||||
Time to be set.
|
||||
|
||||
### param: Clock.setFixedTime.time
|
||||
* langs: csharp
|
||||
* since: v1.45
|
||||
- `time` <[string]|[Date]>
|
||||
|
||||
Time to be set.
|
||||
|
||||
|
|
@ -225,8 +272,8 @@ page.clock.set_system_time("2020-02-02")
|
|||
```
|
||||
|
||||
```java
|
||||
page.clock().setSystemTime(Instant.now());
|
||||
page.clock().setSystemTime(Instant.parse("2020-02-02"));
|
||||
page.clock().setSystemTime(new Date());
|
||||
page.clock().setSystemTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
|
||||
page.clock().setSystemTime("2020-02-02");
|
||||
```
|
||||
|
||||
|
|
@ -237,5 +284,22 @@ await page.Clock.SetSystemTimeAsync("2020-02-02");
|
|||
```
|
||||
|
||||
### param: Clock.setSystemTime.time
|
||||
* langs: js, java
|
||||
* since: v1.45
|
||||
- `time` <[int]|[string]|[Date]>
|
||||
- `time` <[long]|[string]|[Date]>
|
||||
|
||||
Time to be set in milliseconds.
|
||||
|
||||
### param: Clock.setSystemTime.time
|
||||
* langs: python
|
||||
* since: v1.45
|
||||
- `time` <[float]|[string]|[Date]>
|
||||
|
||||
Time to be set.
|
||||
|
||||
### param: Clock.setSystemTime.time
|
||||
* langs: csharp
|
||||
* since: v1.45
|
||||
- `time` <[string]|[Date]>
|
||||
|
||||
Time to be set.
|
||||
|
|
|
|||
|
|
@ -1817,80 +1817,6 @@ class PageExamples
|
|||
}
|
||||
```
|
||||
|
||||
An example of passing an element handle:
|
||||
|
||||
```js
|
||||
await page.exposeBinding('clicked', async (source, element) => {
|
||||
console.log(await element.textContent());
|
||||
}, { handle: true });
|
||||
await page.setContent(`
|
||||
<script>
|
||||
document.addEventListener('click', event => window.clicked(event.target));
|
||||
</script>
|
||||
<div>Click me</div>
|
||||
<div>Or click me</div>
|
||||
`);
|
||||
```
|
||||
|
||||
```java
|
||||
page.exposeBinding("clicked", (source, args) -> {
|
||||
ElementHandle element = (ElementHandle) args[0];
|
||||
System.out.println(element.textContent());
|
||||
return null;
|
||||
}, new Page.ExposeBindingOptions().setHandle(true));
|
||||
page.setContent("" +
|
||||
"<script>\n" +
|
||||
" document.addEventListener('click', event => window.clicked(event.target));\n" +
|
||||
"</script>\n" +
|
||||
"<div>Click me</div>\n" +
|
||||
"<div>Or click me</div>\n");
|
||||
```
|
||||
|
||||
```python async
|
||||
async def print(source, element):
|
||||
print(await element.text_content())
|
||||
|
||||
await page.expose_binding("clicked", print, handle=true)
|
||||
await page.set_content("""
|
||||
<script>
|
||||
document.addEventListener('click', event => window.clicked(event.target));
|
||||
</script>
|
||||
<div>Click me</div>
|
||||
<div>Or click me</div>
|
||||
""")
|
||||
```
|
||||
|
||||
```python sync
|
||||
def print(source, element):
|
||||
print(element.text_content())
|
||||
|
||||
page.expose_binding("clicked", print, handle=true)
|
||||
page.set_content("""
|
||||
<script>
|
||||
document.addEventListener('click', event => window.clicked(event.target));
|
||||
</script>
|
||||
<div>Click me</div>
|
||||
<div>Or click me</div>
|
||||
""")
|
||||
```
|
||||
|
||||
```csharp
|
||||
var result = new TaskCompletionSource<string>();
|
||||
await page.ExposeBindingAsync("clicked", async (BindingSource _, IJSHandle t) =>
|
||||
{
|
||||
return result.TrySetResult(await t.AsElement().TextContentAsync());
|
||||
});
|
||||
|
||||
await page.SetContentAsync("<script>\n" +
|
||||
" document.addEventListener('click', event => window.clicked(event.target));\n" +
|
||||
"</script>\n" +
|
||||
"<div>Click me</div>\n" +
|
||||
"<div>Or click me</div>\n");
|
||||
|
||||
await page.ClickAsync("div");
|
||||
Console.WriteLine(await result.Task);
|
||||
```
|
||||
|
||||
### param: Page.exposeBinding.name
|
||||
* since: v1.8
|
||||
- `name` <[string]>
|
||||
|
|
@ -1905,6 +1831,7 @@ Callback function that will be called in the Playwright's context.
|
|||
|
||||
### option: Page.exposeBinding.handle
|
||||
* since: v1.8
|
||||
* deprecated: This option will be removed in the future.
|
||||
- `handle` <[boolean]>
|
||||
|
||||
Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
|
||||
|
|
|
|||
|
|
@ -118,13 +118,14 @@ expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM"
|
|||
```java
|
||||
// Initialize clock with some time before the test time and let the page load
|
||||
// naturally. `Date.now` will progress as the timers fire.
|
||||
page.clock().install(new Clock.InstallOptions().setTime(Instant.parse("2024-02-02T08:00:00")));
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
|
||||
page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-02-02T08:00:00")));
|
||||
page.navigate("http://localhost:3333");
|
||||
Locator locator = page.getByTestId("current-time");
|
||||
|
||||
// Pretend that the user closed the laptop lid and opened it again at 10am.
|
||||
// Pause the time once reached that point.
|
||||
page.clock().pauseAt(Instant.parse("2024-02-02T10:00:00"));
|
||||
page.clock().pauseAt(format.parse("2024-02-02T10:00:00"));
|
||||
|
||||
// Assert the page state.
|
||||
assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
|
||||
|
|
@ -315,15 +316,16 @@ expect(locator).to_have_text("2/2/2024, 10:00:02 AM")
|
|||
```
|
||||
|
||||
```java
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
|
||||
// Initialize clock with a specific time, let the page load naturally.
|
||||
page.clock().install(new Clock.InstallOptions()
|
||||
.setTime(Instant.parse("2024-02-02T08:00:00")));
|
||||
.setTime(format.parse("2024-02-02T08:00:00")));
|
||||
page.navigate("http://localhost:3333");
|
||||
Locator locator = page.getByTestId("current-time");
|
||||
|
||||
// Pause the time flow, stop the timers, you now have manual control
|
||||
// over the page time.
|
||||
page.clock().pauseAt(Instant.parse("2024-02-02T10:00:00"));
|
||||
page.clock().pauseAt(format.parse("2024-02-02T10:00:00"));
|
||||
assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
|
||||
|
||||
// Tick through time manually, firing all timers in the process.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ title: "Dialogs"
|
|||
|
||||
## Introduction
|
||||
|
||||
Playwright can interact with the web page dialogs such as [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert), [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm), [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) as well as [`beforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event) confirmation.
|
||||
Playwright can interact with the web page dialogs such as [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert), [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm), [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) as well as [`beforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event) confirmation. For print dialogs, see [Print](#print-dialogs).
|
||||
|
||||
## alert(), confirm(), prompt() dialogs
|
||||
|
||||
|
|
@ -126,3 +126,55 @@ Page.Dialog += async (_, dialog) =>
|
|||
};
|
||||
await Page.CloseAsync(new() { RunBeforeUnload = true });
|
||||
```
|
||||
|
||||
## Print dialogs
|
||||
|
||||
In order to assert that a print dialog via [`window.print`](https://developer.mozilla.org/en-US/docs/Web/API/Window/print) was triggered, you can use the following snippet:
|
||||
|
||||
```js
|
||||
await page.goto('<url>');
|
||||
|
||||
await page.evaluate('(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()');
|
||||
await page.getByText('Print it!').click();
|
||||
|
||||
await page.waitForFunction('window.waitForPrintDialog');
|
||||
```
|
||||
|
||||
```java
|
||||
page.navigate("<url>");
|
||||
|
||||
page.evaluate("(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()");
|
||||
page.getByText("Print it!").click();
|
||||
|
||||
page.waitForFunction("window.waitForPrintDialog");
|
||||
```
|
||||
|
||||
```python async
|
||||
await page.goto("<url>")
|
||||
|
||||
await page.evaluate("(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()")
|
||||
await page.get_by_text("Print it!").click()
|
||||
|
||||
await page.wait_for_function("window.waitForPrintDialog")
|
||||
```
|
||||
|
||||
```python sync
|
||||
page.goto("<url>")
|
||||
|
||||
page.evaluate("(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()")
|
||||
page.get_by_text("Print it!").click()
|
||||
|
||||
page.wait_for_function("window.waitForPrintDialog")
|
||||
```
|
||||
|
||||
```csharp
|
||||
await Page.GotoAsync("<url>");
|
||||
|
||||
await Page.EvaluateAsync("(() => {window.waitForPrintDialog = new Promise(f => window.print = f);})()");
|
||||
await Page.GetByText("Print it!").ClickAsync();
|
||||
|
||||
await Page.WaitForFunctionAsync("window.waitForPrintDialog");
|
||||
```
|
||||
|
||||
This will wait for the print dialog to be opened after the button is clicked.
|
||||
Make sure to evaluate the script before clicking the button / after the page is loaded.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,69 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.45
|
||||
|
||||
### Clock
|
||||
|
||||
Utilizing the new [Clock] API allows to manipulate and control time within tests to verify time-related behavior. This API covers many common scenarios, including:
|
||||
* testing with predefined time;
|
||||
* keeping consistent time and timers;
|
||||
* monitoring inactivity;
|
||||
* ticking through time manually.
|
||||
|
||||
```csharp
|
||||
// Initialize clock with some time before the test time and let the page load naturally.
|
||||
// `Date.now` will progress as the timers fire.
|
||||
await Page.Clock.InstallAsync(new()
|
||||
{
|
||||
TimeDate = new DateTime(2024, 2, 2, 8, 0, 0)
|
||||
});
|
||||
await Page.GotoAsync("http://localhost:3333");
|
||||
|
||||
// Pretend that the user closed the laptop lid and opened it again at 10am.
|
||||
// Pause the time once reached that point.
|
||||
await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0));
|
||||
|
||||
// Assert the page state.
|
||||
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:00:00 AM");
|
||||
|
||||
// Close the laptop lid again and open it at 10:30am.
|
||||
await Page.Clock.FastForwardAsync("30:00");
|
||||
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:30:00 AM");
|
||||
```
|
||||
|
||||
See [the clock guide](./clock.md) for more details.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- Method [`method: Locator.setInputFiles`] now supports uploading a directory for `<input type=file webkitdirectory>` elements.
|
||||
```csharp
|
||||
await page.GetByLabel("Upload directory").SetInputFilesAsync("mydir");
|
||||
```
|
||||
|
||||
- Multiple methods like [`method: Locator.click`] or [`method: Locator.press`] now support a `ControlOrMeta` modifier key. This key maps to `Meta` on macOS and maps to `Control` on Windows and Linux.
|
||||
```csharp
|
||||
// Press the common keyboard shortcut Control+S or Meta+S to trigger a "Save" operation.
|
||||
await page.Keyboard.PressAsync("ControlOrMeta+S");
|
||||
```
|
||||
|
||||
- New property `httpCredentials.send` in [`method: APIRequest.newContext`] that allows to either always send the `Authorization` header or only send it in response to `401 Unauthorized`.
|
||||
|
||||
- Playwright now supports Chromium, Firefox and WebKit on Ubuntu 24.04.
|
||||
|
||||
- v1.45 is the last release to receive WebKit update for macOS 12 Monterey. Please update macOS to keep using the latest WebKit.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 127.0.6533.5
|
||||
* Mozilla Firefox 127.0
|
||||
* WebKit 17.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 126
|
||||
* Microsoft Edge 126
|
||||
|
||||
## Version 1.44
|
||||
|
||||
### New APIs
|
||||
|
|
|
|||
|
|
@ -4,6 +4,67 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.45
|
||||
|
||||
### Clock
|
||||
|
||||
Utilizing the new [Clock] API allows to manipulate and control time within tests to verify time-related behavior. This API covers many common scenarios, including:
|
||||
* testing with predefined time;
|
||||
* keeping consistent time and timers;
|
||||
* monitoring inactivity;
|
||||
* ticking through time manually.
|
||||
|
||||
```java
|
||||
// Initialize clock with some time before the test time and let the page load
|
||||
// naturally. `Date.now` will progress as the timers fire.
|
||||
page.clock().install(new Clock.InstallOptions().setTime("2024-02-02T08:00:00"));
|
||||
page.navigate("http://localhost:3333");
|
||||
Locator locator = page.getByTestId("current-time");
|
||||
|
||||
// Pretend that the user closed the laptop lid and opened it again at 10am.
|
||||
// Pause the time once reached that point.
|
||||
page.clock().pauseAt("2024-02-02T10:00:00");
|
||||
|
||||
// Assert the page state.
|
||||
assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
|
||||
|
||||
// Close the laptop lid again and open it at 10:30am.
|
||||
page.clock().fastForward("30:00");
|
||||
assertThat(locator).hasText("2/2/2024, 10:30:00 AM");
|
||||
```
|
||||
|
||||
See [the clock guide](./clock.md) for more details.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- Method [`method: Locator.setInputFiles`] now supports uploading a directory for `<input type=file webkitdirectory>` elements.
|
||||
```java
|
||||
page.getByLabel("Upload directory").setInputFiles(Paths.get("mydir"));
|
||||
```
|
||||
|
||||
- Multiple methods like [`method: Locator.click`] or [`method: Locator.press`] now support a `ControlOrMeta` modifier key. This key maps to `Meta` on macOS and maps to `Control` on Windows and Linux.
|
||||
```java
|
||||
// Press the common keyboard shortcut Control+S or Meta+S to trigger a "Save" operation.
|
||||
page.keyboard.press("ControlOrMeta+S");
|
||||
```
|
||||
|
||||
- New property `httpCredentials.send` in [`method: APIRequest.newContext`] that allows to either always send the `Authorization` header or only send it in response to `401 Unauthorized`.
|
||||
|
||||
- Playwright now supports Chromium, Firefox and WebKit on Ubuntu 24.04.
|
||||
|
||||
- v1.45 is the last release to receive WebKit update for macOS 12 Monterey. Please update macOS to keep using the latest WebKit.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 127.0.6533.5
|
||||
* Mozilla Firefox 127.0
|
||||
* WebKit 17.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 126
|
||||
* Microsoft Edge 126
|
||||
|
||||
## Version 1.44
|
||||
|
||||
### New APIs
|
||||
|
|
|
|||
|
|
@ -43,10 +43,10 @@ See [the clock guide](./clock.md) for more details.
|
|||
|
||||
```sh
|
||||
# Avoid TTY features that output ANSI control sequences
|
||||
PLAYWRIGHT_FORCE_TTY=0 npx playwrigh test
|
||||
PLAYWRIGHT_FORCE_TTY=0 npx playwright test
|
||||
|
||||
# Enable TTY features, assuming a terminal width 80
|
||||
PLAYWRIGHT_FORCE_TTY=80 npx playwrigh test
|
||||
PLAYWRIGHT_FORCE_TTY=80 npx playwright test
|
||||
```
|
||||
|
||||
- New options [`property: TestConfig.respectGitIgnore`] and [`property: TestProject.respectGitIgnore`] control whether files matching `.gitignore` patterns are excluded when searching for tests.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,66 @@ title: "Release notes"
|
|||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
## Version 1.45
|
||||
|
||||
### Clock
|
||||
|
||||
Utilizing the new [Clock] API allows to manipulate and control time within tests to verify time-related behavior. This API covers many common scenarios, including:
|
||||
* testing with predefined time;
|
||||
* keeping consistent time and timers;
|
||||
* monitoring inactivity;
|
||||
* ticking through time manually.
|
||||
|
||||
```python
|
||||
# Initialize clock with some time before the test time and let the page load
|
||||
# naturally. `Date.now` will progress as the timers fire.
|
||||
page.clock.install(time=datetime.datetime(2024, 2, 2, 8, 0, 0))
|
||||
page.goto("http://localhost:3333")
|
||||
|
||||
# Pretend that the user closed the laptop lid and opened it again at 10am.
|
||||
# Pause the time once reached that point.
|
||||
page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0))
|
||||
|
||||
# Assert the page state.
|
||||
expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM")
|
||||
|
||||
# Close the laptop lid again and open it at 10:30am.
|
||||
page.clock.fast_forward("30:00")
|
||||
expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM")
|
||||
```
|
||||
|
||||
See [the clock guide](./clock.md) for more details.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- Method [`method: Locator.setInputFiles`] now supports uploading a directory for `<input type=file webkitdirectory>` elements.
|
||||
```python
|
||||
page.get_by_label("Upload directory").set_input_files('mydir')
|
||||
```
|
||||
|
||||
- Multiple methods like [`method: Locator.click`] or [`method: Locator.press`] now support a `ControlOrMeta` modifier key. This key maps to `Meta` on macOS and maps to `Control` on Windows and Linux.
|
||||
```python
|
||||
# Press the common keyboard shortcut Control+S or Meta+S to trigger a "Save" operation.
|
||||
page.keyboard.press("ControlOrMeta+S")
|
||||
```
|
||||
|
||||
- New property `httpCredentials.send` in [`method: APIRequest.newContext`] that allows to either always send the `Authorization` header or only send it in response to `401 Unauthorized`.
|
||||
|
||||
- Playwright now supports Chromium, Firefox and WebKit on Ubuntu 24.04.
|
||||
|
||||
- v1.45 is the last release to receive WebKit update for macOS 12 Monterey. Please update macOS to keep using the latest WebKit.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
* Chromium 127.0.6533.5
|
||||
* Mozilla Firefox 127.0
|
||||
* WebKit 17.4
|
||||
|
||||
This version was also tested against the following stable channels:
|
||||
|
||||
* Google Chrome 126
|
||||
* Microsoft Edge 126
|
||||
|
||||
## Version 1.44
|
||||
|
||||
### New APIs
|
||||
|
|
|
|||
|
|
@ -9,13 +9,61 @@ You can either parameterize tests on a test level or on a project level.
|
|||
## Parameterized Tests
|
||||
|
||||
```js title="example.spec.ts"
|
||||
const people = ['Alice', 'Bob'];
|
||||
for (const name of people) {
|
||||
test(`testing with ${name}`, async () => {
|
||||
// ...
|
||||
});
|
||||
[
|
||||
{ name: 'Alice', expected: 'Hello, Alice!' },
|
||||
{ name: 'Bob', expected: 'Hello, Bob!' },
|
||||
{ name: 'Charlie', expected: 'Hello, Charlie!' },
|
||||
].forEach(({ name, expected }) => {
|
||||
// You can also do it with test.describe() or with multiple tests as long the test name is unique.
|
||||
}
|
||||
test(`testing with ${name}`, async ({ page }) => {
|
||||
await page.goto(`https://example.com/greet?name=${name}`);
|
||||
await expect(page.getByRole('heading')).toHaveText(expected);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Before and after hooks
|
||||
|
||||
Most of the time you should put `beforeEach`, `beforeAll`, `afterEach` and `afterAll` hooks outside of `forEach`, so that hooks are executed just once:
|
||||
|
||||
```js title="example.spec.ts"
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
[
|
||||
{ name: 'Alice', expected: 'Hello, Alice!' },
|
||||
{ name: 'Bob', expected: 'Hello, Bob!' },
|
||||
{ name: 'Charlie', expected: 'Hello, Charlie!' },
|
||||
].forEach(({ name, expected }) => {
|
||||
test(`testing with ${name}`, async ({ page }) => {
|
||||
await page.goto(`https://example.com/greet?name=${name}`);
|
||||
await expect(page.getByRole('heading')).toHaveText(expected);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
If you want to have hooks for each test, you can put them inside a `describe()` - so they are executed for each iteration / each invidual test:
|
||||
|
||||
```js title="example.spec.ts"
|
||||
[
|
||||
{ name: 'Alice', expected: 'Hello, Alice!' },
|
||||
{ name: 'Bob', expected: 'Hello, Bob!' },
|
||||
{ name: 'Charlie', expected: 'Hello, Charlie!' },
|
||||
].forEach(({ name, expected }) => {
|
||||
test.describe(() => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(`https://example.com/greet?name=${name}`);
|
||||
});
|
||||
test(`testing with ${expected}`, async ({ page }) => {
|
||||
await expect(page.getByRole('heading')).toHaveText(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Parameterized Projects
|
||||
|
|
|
|||
68
package-lock.json
generated
68
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "playwright-internal",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "playwright-internal",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"license": "Apache-2.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
|
|
@ -8162,10 +8162,10 @@
|
|||
}
|
||||
},
|
||||
"packages/playwright": {
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
|
@ -8179,11 +8179,11 @@
|
|||
},
|
||||
"packages/playwright-browser-chromium": {
|
||||
"name": "@playwright/browser-chromium",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
|
@ -8191,11 +8191,11 @@
|
|||
},
|
||||
"packages/playwright-browser-firefox": {
|
||||
"name": "@playwright/browser-firefox",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
|
@ -8203,22 +8203,22 @@
|
|||
},
|
||||
"packages/playwright-browser-webkit": {
|
||||
"name": "@playwright/browser-webkit",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"packages/playwright-chromium": {
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
|
@ -8228,7 +8228,7 @@
|
|||
}
|
||||
},
|
||||
"packages/playwright-core": {
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
|
|
@ -8239,11 +8239,11 @@
|
|||
},
|
||||
"packages/playwright-ct-core": {
|
||||
"name": "@playwright/experimental-ct-core",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.45.0-next",
|
||||
"playwright-core": "1.45.0-next",
|
||||
"playwright": "1.45.3",
|
||||
"playwright-core": "1.45.3",
|
||||
"vite": "^5.2.8"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -8252,10 +8252,10 @@
|
|||
},
|
||||
"packages/playwright-ct-react": {
|
||||
"name": "@playwright/experimental-ct-react",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.45.3",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -8267,10 +8267,10 @@
|
|||
},
|
||||
"packages/playwright-ct-react17": {
|
||||
"name": "@playwright/experimental-ct-react17",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.45.3",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -8282,10 +8282,10 @@
|
|||
},
|
||||
"packages/playwright-ct-solid": {
|
||||
"name": "@playwright/experimental-ct-solid",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.45.3",
|
||||
"vite-plugin-solid": "^2.7.0"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -8300,10 +8300,10 @@
|
|||
},
|
||||
"packages/playwright-ct-svelte": {
|
||||
"name": "@playwright/experimental-ct-svelte",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.45.3",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.1"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -8318,10 +8318,10 @@
|
|||
},
|
||||
"packages/playwright-ct-vue": {
|
||||
"name": "@playwright/experimental-ct-vue",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.45.3",
|
||||
"@vitejs/plugin-vue": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -8333,10 +8333,10 @@
|
|||
},
|
||||
"packages/playwright-ct-vue2": {
|
||||
"name": "@playwright/experimental-ct-vue2",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.45.3",
|
||||
"@vitejs/plugin-vue2": "^2.2.0"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -8385,11 +8385,11 @@
|
|||
}
|
||||
},
|
||||
"packages/playwright-firefox": {
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
|
@ -8400,10 +8400,10 @@
|
|||
},
|
||||
"packages/playwright-test": {
|
||||
"name": "@playwright/test",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.45.0-next"
|
||||
"playwright": "1.45.3"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
|
@ -8413,11 +8413,11 @@
|
|||
}
|
||||
},
|
||||
"packages/playwright-webkit": {
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "playwright-internal",
|
||||
"private": true,
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/browser-chromium",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "Playwright package that automatically installs Chromium",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -27,6 +27,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/browser-firefox",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "Playwright package that automatically installs Firefox",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -27,6 +27,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/browser-webkit",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "Playwright package that automatically installs WebKit",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -27,6 +27,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-chromium",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "A high-level API to automate Chromium",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -30,6 +30,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
"browsers": [
|
||||
{
|
||||
"name": "chromium",
|
||||
"revision": "1123",
|
||||
"revision": "1124",
|
||||
"installByDefault": true,
|
||||
"browserVersion": "127.0.6533.5"
|
||||
"browserVersion": "127.0.6533.17"
|
||||
},
|
||||
{
|
||||
"name": "chromium-tip-of-tree",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-core",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ function parseTime(time: string | number | Date): { timeNumber?: number, timeStr
|
|||
return { timeNumber: time };
|
||||
if (typeof time === 'string')
|
||||
return { timeString: time };
|
||||
if (!isFinite(time.getTime()))
|
||||
throw new Error(`Invalid date: ${time}`);
|
||||
return { timeNumber: time.getTime() };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
|||
}
|
||||
|
||||
async start(options: { name?: string, title?: string, snapshots?: boolean, screenshots?: boolean, sources?: boolean, _live?: boolean } = {}) {
|
||||
await this._wrapApiCall(async () => {
|
||||
this._includeSources = !!options.sources;
|
||||
this._includeSources = !!options.sources;
|
||||
const traceName = await this._wrapApiCall(async () => {
|
||||
await this._channel.tracingStart({
|
||||
name: options.name,
|
||||
snapshots: options.snapshots,
|
||||
|
|
@ -43,15 +43,14 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
|
|||
live: options._live,
|
||||
});
|
||||
const response = await this._channel.tracingStartChunk({ name: options.name, title: options.title });
|
||||
await this._startCollectingStacks(response.traceName);
|
||||
return response.traceName;
|
||||
}, true);
|
||||
await this._startCollectingStacks(traceName);
|
||||
}
|
||||
|
||||
async startChunk(options: { name?: string, title?: string } = {}) {
|
||||
await this._wrapApiCall(async () => {
|
||||
const { traceName } = await this._channel.tracingStartChunk(options);
|
||||
await this._startCollectingStacks(traceName);
|
||||
}, true);
|
||||
const { traceName } = await this._channel.tracingStartChunk(options);
|
||||
await this._startCollectingStacks(traceName);
|
||||
}
|
||||
|
||||
private async _startCollectingStacks(traceName: string) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import type { Download } from './download';
|
|||
import type * as frames from './frames';
|
||||
import { helper } from './helper';
|
||||
import * as network from './network';
|
||||
import { InitScript } from './page';
|
||||
import type { PageDelegate } from './page';
|
||||
import { Page, PageBinding } from './page';
|
||||
import type { Progress, ProgressController } from './progress';
|
||||
|
|
@ -84,7 +85,7 @@ export abstract class BrowserContext extends SdkObject {
|
|||
private _customCloseHandler?: () => Promise<any>;
|
||||
readonly _tempDirs: string[] = [];
|
||||
private _settingStorageState = false;
|
||||
readonly initScripts: string[] = [];
|
||||
readonly initScripts: InitScript[] = [];
|
||||
private _routesInFlight = new Set<network.Route>();
|
||||
private _debugger!: Debugger;
|
||||
_closeReason: string | undefined;
|
||||
|
|
@ -216,6 +217,7 @@ export abstract class BrowserContext extends SdkObject {
|
|||
await this._resetStorage();
|
||||
await this._removeExposedBindings();
|
||||
await this._removeInitScripts();
|
||||
this.clock.markAsUninstalled();
|
||||
// TODO: following can be optimized to not perform noops.
|
||||
if (this._options.permissions)
|
||||
await this.grantPermissions(this._options.permissions);
|
||||
|
|
@ -265,7 +267,7 @@ export abstract class BrowserContext extends SdkObject {
|
|||
protected abstract doGrantPermissions(origin: string, permissions: string[]): Promise<void>;
|
||||
protected abstract doClearPermissions(): Promise<void>;
|
||||
protected abstract doSetHTTPCredentials(httpCredentials?: types.Credentials): Promise<void>;
|
||||
protected abstract doAddInitScript(expression: string): Promise<void>;
|
||||
protected abstract doAddInitScript(initScript: InitScript): Promise<void>;
|
||||
protected abstract doRemoveInitScripts(): Promise<void>;
|
||||
protected abstract doExposeBinding(binding: PageBinding): Promise<void>;
|
||||
protected abstract doRemoveExposedBindings(): Promise<void>;
|
||||
|
|
@ -402,9 +404,10 @@ export abstract class BrowserContext extends SdkObject {
|
|||
this._options.httpCredentials = { username, password: password || '' };
|
||||
}
|
||||
|
||||
async addInitScript(script: string) {
|
||||
this.initScripts.push(script);
|
||||
await this.doAddInitScript(script);
|
||||
async addInitScript(source: string) {
|
||||
const initScript = new InitScript(source);
|
||||
this.initScripts.push(initScript);
|
||||
await this.doAddInitScript(initScript);
|
||||
}
|
||||
|
||||
async _removeInitScripts(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -305,12 +305,9 @@ export class Chromium extends BrowserType {
|
|||
if (os.platform() === 'darwin') {
|
||||
// See https://github.com/microsoft/playwright/issues/7362
|
||||
chromeArguments.push('--enable-use-zoom-for-dsf=false');
|
||||
}
|
||||
if (options.headless) {
|
||||
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1407025.
|
||||
// See also https://github.com/microsoft/playwright/issues/30585
|
||||
// and chromium fix at https://issues.chromium.org/issues/338414704.
|
||||
chromeArguments.push('--enable-gpu');
|
||||
if (options.headless)
|
||||
chromeArguments.push('--use-angle');
|
||||
}
|
||||
|
||||
if (options.devtools)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { Browser } from '../browser';
|
|||
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
|
||||
import { assert, createGuid } from '../../utils';
|
||||
import * as network from '../network';
|
||||
import type { PageBinding, PageDelegate, Worker } from '../page';
|
||||
import type { InitScript, PageBinding, PageDelegate, Worker } from '../page';
|
||||
import { Page } from '../page';
|
||||
import { Frame } from '../frames';
|
||||
import type { Dialog } from '../dialog';
|
||||
|
|
@ -486,9 +486,9 @@ export class CRBrowserContext extends BrowserContext {
|
|||
await (sw as CRServiceWorker).updateHttpCredentials();
|
||||
}
|
||||
|
||||
async doAddInitScript(source: string) {
|
||||
async doAddInitScript(initScript: InitScript) {
|
||||
for (const page of this.pages())
|
||||
await (page._delegate as CRPage).addInitScript(source);
|
||||
await (page._delegate as CRPage).addInitScript(initScript);
|
||||
}
|
||||
|
||||
async doRemoveInitScripts() {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import * as dom from '../dom';
|
|||
import * as frames from '../frames';
|
||||
import { helper } from '../helper';
|
||||
import * as network from '../network';
|
||||
import type { PageBinding, PageDelegate } from '../page';
|
||||
import type { InitScript, PageBinding, PageDelegate } from '../page';
|
||||
import { Page, Worker } from '../page';
|
||||
import type { Progress } from '../progress';
|
||||
import type * as types from '../types';
|
||||
|
|
@ -256,8 +256,8 @@ export class CRPage implements PageDelegate {
|
|||
return this._go(+1);
|
||||
}
|
||||
|
||||
async addInitScript(source: string, world: types.World = 'main'): Promise<void> {
|
||||
await this._forAllFrameSessions(frame => frame._evaluateOnNewDocument(source, world));
|
||||
async addInitScript(initScript: InitScript, world: types.World = 'main'): Promise<void> {
|
||||
await this._forAllFrameSessions(frame => frame._evaluateOnNewDocument(initScript, world));
|
||||
}
|
||||
|
||||
async removeInitScripts() {
|
||||
|
|
@ -511,6 +511,20 @@ class FrameSession {
|
|||
this._addRendererListeners();
|
||||
}
|
||||
|
||||
const localFrames = this._isMainFrame() ? this._page.frames() : [this._page._frameManager.frame(this._targetId)!];
|
||||
for (const frame of localFrames) {
|
||||
// Note: frames might be removed before we send these.
|
||||
this._client._sendMayFail('Page.createIsolatedWorld', {
|
||||
frameId: frame._id,
|
||||
grantUniveralAccess: true,
|
||||
worldName: UTILITY_WORLD_NAME,
|
||||
});
|
||||
for (const binding of this._crPage._browserContext._pageBindings.values())
|
||||
frame.evaluateExpression(binding.source).catch(e => {});
|
||||
for (const initScript of this._crPage._browserContext.initScripts)
|
||||
frame.evaluateExpression(initScript.source).catch(e => {});
|
||||
}
|
||||
|
||||
const isInitialEmptyPage = this._isMainFrame() && this._page.mainFrame().url() === ':';
|
||||
if (isInitialEmptyPage) {
|
||||
// Ignore lifecycle events, worlds and bindings for the initial empty page. It is never the final page
|
||||
|
|
@ -520,20 +534,6 @@ class FrameSession {
|
|||
this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event)));
|
||||
});
|
||||
} else {
|
||||
const localFrames = this._isMainFrame() ? this._page.frames() : [this._page._frameManager.frame(this._targetId)!];
|
||||
for (const frame of localFrames) {
|
||||
// Note: frames might be removed before we send these.
|
||||
this._client._sendMayFail('Page.createIsolatedWorld', {
|
||||
frameId: frame._id,
|
||||
grantUniveralAccess: true,
|
||||
worldName: UTILITY_WORLD_NAME,
|
||||
});
|
||||
for (const binding of this._crPage._browserContext._pageBindings.values())
|
||||
frame.evaluateExpression(binding.source).catch(e => {});
|
||||
for (const source of this._crPage._browserContext.initScripts)
|
||||
frame.evaluateExpression(source).catch(e => {});
|
||||
}
|
||||
|
||||
this._firstNonInitialNavigationCommittedFulfill();
|
||||
this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event)));
|
||||
}
|
||||
|
|
@ -575,10 +575,10 @@ class FrameSession {
|
|||
promises.push(this._updateFileChooserInterception(true));
|
||||
for (const binding of this._crPage._page.allBindings())
|
||||
promises.push(this._initBinding(binding));
|
||||
for (const source of this._crPage._browserContext.initScripts)
|
||||
promises.push(this._evaluateOnNewDocument(source, 'main'));
|
||||
for (const source of this._crPage._page.initScripts)
|
||||
promises.push(this._evaluateOnNewDocument(source, 'main'));
|
||||
for (const initScript of this._crPage._browserContext.initScripts)
|
||||
promises.push(this._evaluateOnNewDocument(initScript, 'main'));
|
||||
for (const initScript of this._crPage._page.initScripts)
|
||||
promises.push(this._evaluateOnNewDocument(initScript, 'main'));
|
||||
if (screencastOptions)
|
||||
promises.push(this._startVideoRecording(screencastOptions));
|
||||
}
|
||||
|
|
@ -1099,9 +1099,9 @@ class FrameSession {
|
|||
await this._client.send('Page.setInterceptFileChooserDialog', { enabled }).catch(() => {}); // target can be closed.
|
||||
}
|
||||
|
||||
async _evaluateOnNewDocument(source: string, world: types.World): Promise<void> {
|
||||
async _evaluateOnNewDocument(initScript: InitScript, world: types.World): Promise<void> {
|
||||
const worldName = world === 'utility' ? UTILITY_WORLD_NAME : undefined;
|
||||
const { identifier } = await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source, worldName });
|
||||
const { identifier } = await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source: initScript.source, worldName });
|
||||
this._evaluateOnNewDocumentIdentifiers.push(identifier);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ export class Clock {
|
|||
this._browserContext = browserContext;
|
||||
}
|
||||
|
||||
markAsUninstalled() {
|
||||
this._scriptInstalled = false;
|
||||
}
|
||||
|
||||
async fastForward(ticks: number | string) {
|
||||
await this._installIfNeeded();
|
||||
const ticksMillis = parseTicks(ticks);
|
||||
|
|
@ -144,5 +148,8 @@ function parseTime(epoch: string | number | undefined): number {
|
|||
return 0;
|
||||
if (typeof epoch === 'number')
|
||||
return epoch;
|
||||
return new Date(epoch).getTime();
|
||||
const parsed = new Date(epoch);
|
||||
if (!isFinite(parsed.getTime()))
|
||||
throw new Error(`Invalid date: ${epoch}`);
|
||||
return parsed.getTime();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Galaxy S5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -121,7 +121,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -132,7 +132,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S8": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 740
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S8 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 740,
|
||||
"height": 360
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S9+": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 320,
|
||||
"height": 658
|
||||
|
|
@ -165,7 +165,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy S9+ landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 658,
|
||||
"height": 320
|
||||
|
|
@ -176,7 +176,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy Tab S4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 712,
|
||||
"height": 1138
|
||||
|
|
@ -187,7 +187,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Galaxy Tab S4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 1138,
|
||||
"height": 712
|
||||
|
|
@ -978,7 +978,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"LG Optimus L70": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 384,
|
||||
"height": 640
|
||||
|
|
@ -989,7 +989,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"LG Optimus L70 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 384
|
||||
|
|
@ -1000,7 +1000,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Microsoft Lumia 550": {
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36 Edge/14.14263",
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1011,7 +1011,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Microsoft Lumia 550 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36 Edge/14.14263",
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1022,7 +1022,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Microsoft Lumia 950": {
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36 Edge/14.14263",
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1033,7 +1033,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Microsoft Lumia 950 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36 Edge/14.14263",
|
||||
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1044,7 +1044,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 10": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 800,
|
||||
"height": 1280
|
||||
|
|
@ -1055,7 +1055,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 10 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
|
|
@ -1066,7 +1066,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 384,
|
||||
"height": 640
|
||||
|
|
@ -1077,7 +1077,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 384
|
||||
|
|
@ -1088,7 +1088,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1099,7 +1099,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1110,7 +1110,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5X": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1121,7 +1121,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5X landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1132,7 +1132,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1143,7 +1143,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1154,7 +1154,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6P": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1165,7 +1165,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 6P landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1176,7 +1176,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 7": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 600,
|
||||
"height": 960
|
||||
|
|
@ -1187,7 +1187,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 7 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 960,
|
||||
"height": 600
|
||||
|
|
@ -1242,7 +1242,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Pixel 2": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 411,
|
||||
"height": 731
|
||||
|
|
@ -1253,7 +1253,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 2 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 731,
|
||||
"height": 411
|
||||
|
|
@ -1264,7 +1264,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 2 XL": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 411,
|
||||
"height": 823
|
||||
|
|
@ -1275,7 +1275,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 2 XL landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 823,
|
||||
"height": 411
|
||||
|
|
@ -1286,7 +1286,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 3": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 393,
|
||||
"height": 786
|
||||
|
|
@ -1297,7 +1297,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 3 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 786,
|
||||
"height": 393
|
||||
|
|
@ -1308,7 +1308,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 353,
|
||||
"height": 745
|
||||
|
|
@ -1319,7 +1319,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 745,
|
||||
"height": 353
|
||||
|
|
@ -1330,7 +1330,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4a (5G)": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 412,
|
||||
"height": 892
|
||||
|
|
@ -1345,7 +1345,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4a (5G) landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"height": 892,
|
||||
"width": 412
|
||||
|
|
@ -1360,7 +1360,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 393,
|
||||
"height": 851
|
||||
|
|
@ -1375,7 +1375,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 851,
|
||||
"height": 393
|
||||
|
|
@ -1390,7 +1390,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 7": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 412,
|
||||
"height": 915
|
||||
|
|
@ -1405,7 +1405,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 7 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 915,
|
||||
"height": 412
|
||||
|
|
@ -1420,7 +1420,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Moto G4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1431,7 +1431,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Moto G4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1442,7 +1442,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Chrome HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1457,7 +1457,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Edge HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36 Edg/127.0.6533.5",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36 Edg/127.0.6533.17",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1502,7 +1502,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Desktop Chrome": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
@ -1517,7 +1517,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Edge": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.5 Safari/537.36 Edg/127.0.6533.5",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36 Edg/127.0.6533.17",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import type { BrowserOptions } from '../browser';
|
|||
import { Browser } from '../browser';
|
||||
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
|
||||
import * as network from '../network';
|
||||
import type { Page, PageBinding, PageDelegate } from '../page';
|
||||
import type { InitScript, Page, PageBinding, PageDelegate } from '../page';
|
||||
import type { ConnectionTransport } from '../transport';
|
||||
import type * as types from '../types';
|
||||
import type * as channels from '@protocol/channels';
|
||||
|
|
@ -352,8 +352,8 @@ export class FFBrowserContext extends BrowserContext {
|
|||
await this._browser.session.send('Browser.setHTTPCredentials', { browserContextId: this._browserContextId, credentials });
|
||||
}
|
||||
|
||||
async doAddInitScript(source: string) {
|
||||
await this._browser.session.send('Browser.setInitScripts', { browserContextId: this._browserContextId, scripts: this.initScripts.map(script => ({ script })) });
|
||||
async doAddInitScript(initScript: InitScript) {
|
||||
await this._browser.session.send('Browser.setInitScripts', { browserContextId: this._browserContextId, scripts: this.initScripts.map(script => ({ script: script.source })) });
|
||||
}
|
||||
|
||||
async doRemoveInitScripts() {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import type * as frames from '../frames';
|
|||
import type { RegisteredListener } from '../../utils/eventsHelper';
|
||||
import { eventsHelper } from '../../utils/eventsHelper';
|
||||
import type { PageBinding, PageDelegate } from '../page';
|
||||
import { InitScript } from '../page';
|
||||
import { Page, Worker } from '../page';
|
||||
import type * as types from '../types';
|
||||
import { getAccessibilityTree } from './ffAccessibility';
|
||||
|
|
@ -56,7 +57,7 @@ export class FFPage implements PageDelegate {
|
|||
private _eventListeners: RegisteredListener[];
|
||||
private _workers = new Map<string, { frameId: string, session: FFSession }>();
|
||||
private _screencastId: string | undefined;
|
||||
private _initScripts: { script: string, worldName?: string }[] = [];
|
||||
private _initScripts: { initScript: InitScript, worldName?: string }[] = [];
|
||||
|
||||
constructor(session: FFSession, browserContext: FFBrowserContext, opener: FFPage | null) {
|
||||
this._session = session;
|
||||
|
|
@ -113,7 +114,7 @@ export class FFPage implements PageDelegate {
|
|||
});
|
||||
// Ideally, we somehow ensure that utility world is created before Page.ready arrives, but currently it is racy.
|
||||
// Therefore, we can end up with an initialized page without utility world, although very unlikely.
|
||||
this.addInitScript('', UTILITY_WORLD_NAME).catch(e => this._markAsError(e));
|
||||
this.addInitScript(new InitScript(''), UTILITY_WORLD_NAME).catch(e => this._markAsError(e));
|
||||
}
|
||||
|
||||
potentiallyUninitializedPage(): Page {
|
||||
|
|
@ -406,9 +407,9 @@ export class FFPage implements PageDelegate {
|
|||
return success;
|
||||
}
|
||||
|
||||
async addInitScript(script: string, worldName?: string): Promise<void> {
|
||||
this._initScripts.push({ script, worldName });
|
||||
await this._session.send('Page.setInitScripts', { scripts: this._initScripts });
|
||||
async addInitScript(initScript: InitScript, worldName?: string): Promise<void> {
|
||||
this._initScripts.push({ initScript, worldName });
|
||||
await this._session.send('Page.setInitScripts', { scripts: this._initScripts.map(s => ({ script: s.initScript.source, worldName: s.worldName })) });
|
||||
}
|
||||
|
||||
async removeInitScripts() {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import * as accessibility from './accessibility';
|
|||
import { FileChooser } from './fileChooser';
|
||||
import type { Progress } from './progress';
|
||||
import { ProgressController } from './progress';
|
||||
import { LongStandingScope, assert, isError } from '../utils';
|
||||
import { LongStandingScope, assert, createGuid, isError } from '../utils';
|
||||
import { ManualPromise } from '../utils/manualPromise';
|
||||
import { debugLogger } from '../utils/debugLogger';
|
||||
import type { ImageComparatorOptions } from '../utils/comparators';
|
||||
|
|
@ -56,7 +56,7 @@ export interface PageDelegate {
|
|||
goForward(): Promise<boolean>;
|
||||
exposeBinding(binding: PageBinding): Promise<void>;
|
||||
removeExposedBindings(): Promise<void>;
|
||||
addInitScript(source: string): Promise<void>;
|
||||
addInitScript(initScript: InitScript): Promise<void>;
|
||||
removeInitScripts(): Promise<void>;
|
||||
closePage(runBeforeUnload: boolean): Promise<void>;
|
||||
potentiallyUninitializedPage(): Page;
|
||||
|
|
@ -154,7 +154,7 @@ export class Page extends SdkObject {
|
|||
private _emulatedMedia: Partial<EmulatedMedia> = {};
|
||||
private _interceptFileChooser = false;
|
||||
private readonly _pageBindings = new Map<string, PageBinding>();
|
||||
readonly initScripts: string[] = [];
|
||||
readonly initScripts: InitScript[] = [];
|
||||
readonly _screenshotter: Screenshotter;
|
||||
readonly _frameManager: frames.FrameManager;
|
||||
readonly accessibility: accessibility.Accessibility;
|
||||
|
|
@ -527,8 +527,9 @@ export class Page extends SdkObject {
|
|||
}
|
||||
|
||||
async addInitScript(source: string) {
|
||||
this.initScripts.push(source);
|
||||
await this._delegate.addInitScript(source);
|
||||
const initScript = new InitScript(source);
|
||||
this.initScripts.push(initScript);
|
||||
await this._delegate.addInitScript(initScript);
|
||||
}
|
||||
|
||||
async _removeInitScripts() {
|
||||
|
|
@ -905,6 +906,22 @@ function addPageBinding(bindingName: string, needsHandle: boolean, utilityScript
|
|||
(globalThis as any)[bindingName].__installed = true;
|
||||
}
|
||||
|
||||
export class InitScript {
|
||||
readonly source: string;
|
||||
|
||||
constructor(source: string) {
|
||||
const guid = createGuid();
|
||||
this.source = `(() => {
|
||||
globalThis.__pwInitScripts = globalThis.__pwInitScripts || {};
|
||||
const hasInitScript = globalThis.__pwInitScripts[${JSON.stringify(guid)}];
|
||||
if (hasInitScript)
|
||||
return;
|
||||
globalThis.__pwInitScripts[${JSON.stringify(guid)}] = true;
|
||||
${source}
|
||||
})();`;
|
||||
}
|
||||
}
|
||||
|
||||
class FrameThrottler {
|
||||
private _acks: (() => void)[] = [];
|
||||
private _defaultInterval: number;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import type { RegisteredListener } from '../../utils/eventsHelper';
|
|||
import { assert } from '../../utils';
|
||||
import { eventsHelper } from '../../utils/eventsHelper';
|
||||
import * as network from '../network';
|
||||
import type { Page, PageBinding, PageDelegate } from '../page';
|
||||
import type { InitScript, Page, PageBinding, PageDelegate } from '../page';
|
||||
import type { ConnectionTransport } from '../transport';
|
||||
import type * as types from '../types';
|
||||
import type * as channels from '@protocol/channels';
|
||||
|
|
@ -315,7 +315,7 @@ export class WKBrowserContext extends BrowserContext {
|
|||
await (page._delegate as WKPage).updateHttpCredentials();
|
||||
}
|
||||
|
||||
async doAddInitScript(source: string) {
|
||||
async doAddInitScript(initScript: InitScript) {
|
||||
for (const page of this.pages())
|
||||
await (page._delegate as WKPage)._updateBootstrapScript();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import { eventsHelper } from '../../utils/eventsHelper';
|
|||
import { helper } from '../helper';
|
||||
import type { JSHandle } from '../javascript';
|
||||
import * as network from '../network';
|
||||
import type { PageBinding, PageDelegate } from '../page';
|
||||
import type { InitScript, PageBinding, PageDelegate } from '../page';
|
||||
import { Page } from '../page';
|
||||
import type { Progress } from '../progress';
|
||||
import type * as types from '../types';
|
||||
|
|
@ -777,7 +777,7 @@ export class WKPage implements PageDelegate {
|
|||
await this._updateBootstrapScript();
|
||||
}
|
||||
|
||||
async addInitScript(script: string): Promise<void> {
|
||||
async addInitScript(initScript: InitScript): Promise<void> {
|
||||
await this._updateBootstrapScript();
|
||||
}
|
||||
|
||||
|
|
@ -797,8 +797,8 @@ export class WKPage implements PageDelegate {
|
|||
|
||||
for (const binding of this._page.allBindings())
|
||||
scripts.push(binding.source);
|
||||
scripts.push(...this._browserContext.initScripts);
|
||||
scripts.push(...this._page.initScripts);
|
||||
scripts.push(...this._browserContext.initScripts.map(s => s.source));
|
||||
scripts.push(...this._page.initScripts.map(s => s.source));
|
||||
return scripts.join(';\n');
|
||||
}
|
||||
|
||||
|
|
|
|||
66
packages/playwright-core/types/types.d.ts
vendored
66
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -814,21 +814,6 @@ export interface Page {
|
|||
* })();
|
||||
* ```
|
||||
*
|
||||
* An example of passing an element handle:
|
||||
*
|
||||
* ```js
|
||||
* await page.exposeBinding('clicked', async (source, element) => {
|
||||
* console.log(await element.textContent());
|
||||
* }, { handle: true });
|
||||
* await page.setContent(`
|
||||
* <script>
|
||||
* document.addEventListener('click', event => window.clicked(event.target));
|
||||
* </script>
|
||||
* <div>Click me</div>
|
||||
* <div>Or click me</div>
|
||||
* `);
|
||||
* ```
|
||||
*
|
||||
* @param name Name of the function on the window object.
|
||||
* @param callback Callback function that will be called in the Playwright's context.
|
||||
* @param options
|
||||
|
|
@ -875,21 +860,6 @@ export interface Page {
|
|||
* })();
|
||||
* ```
|
||||
*
|
||||
* An example of passing an element handle:
|
||||
*
|
||||
* ```js
|
||||
* await page.exposeBinding('clicked', async (source, element) => {
|
||||
* console.log(await element.textContent());
|
||||
* }, { handle: true });
|
||||
* await page.setContent(`
|
||||
* <script>
|
||||
* document.addEventListener('click', event => window.clicked(event.target));
|
||||
* </script>
|
||||
* <div>Click me</div>
|
||||
* <div>Or click me</div>
|
||||
* `);
|
||||
* ```
|
||||
*
|
||||
* @param name Name of the function on the window object.
|
||||
* @param callback Callback function that will be called in the Playwright's context.
|
||||
* @param options
|
||||
|
|
@ -7637,21 +7607,6 @@ export interface BrowserContext {
|
|||
* })();
|
||||
* ```
|
||||
*
|
||||
* An example of passing an element handle:
|
||||
*
|
||||
* ```js
|
||||
* await context.exposeBinding('clicked', async (source, element) => {
|
||||
* console.log(await element.textContent());
|
||||
* }, { handle: true });
|
||||
* await page.setContent(`
|
||||
* <script>
|
||||
* document.addEventListener('click', event => window.clicked(event.target));
|
||||
* </script>
|
||||
* <div>Click me</div>
|
||||
* <div>Or click me</div>
|
||||
* `);
|
||||
* ```
|
||||
*
|
||||
* @param name Name of the function on the window object.
|
||||
* @param callback Callback function that will be called in the Playwright's context.
|
||||
* @param options
|
||||
|
|
@ -7693,21 +7648,6 @@ export interface BrowserContext {
|
|||
* })();
|
||||
* ```
|
||||
*
|
||||
* An example of passing an element handle:
|
||||
*
|
||||
* ```js
|
||||
* await context.exposeBinding('clicked', async (source, element) => {
|
||||
* console.log(await element.textContent());
|
||||
* }, { handle: true });
|
||||
* await page.setContent(`
|
||||
* <script>
|
||||
* document.addEventListener('click', event => window.clicked(event.target));
|
||||
* </script>
|
||||
* <div>Click me</div>
|
||||
* <div>Or click me</div>
|
||||
* `);
|
||||
* ```
|
||||
*
|
||||
* @param name Name of the function on the window object.
|
||||
* @param callback Callback function that will be called in the Playwright's context.
|
||||
* @param options
|
||||
|
|
@ -17312,7 +17252,7 @@ export interface Clock {
|
|||
* await page.clock.pauseAt('2020-02-02');
|
||||
* ```
|
||||
*
|
||||
* @param time
|
||||
* @param time Time to pause at.
|
||||
*/
|
||||
pauseAt(time: number|string|Date): Promise<void>;
|
||||
|
||||
|
|
@ -17347,7 +17287,7 @@ export interface Clock {
|
|||
* await page.clock.setFixedTime('2020-02-02');
|
||||
* ```
|
||||
*
|
||||
* @param time Time to be set.
|
||||
* @param time Time to be set in milliseconds.
|
||||
*/
|
||||
setFixedTime(time: number|string|Date): Promise<void>;
|
||||
|
||||
|
|
@ -17362,7 +17302,7 @@ export interface Clock {
|
|||
* await page.clock.setSystemTime('2020-02-02');
|
||||
* ```
|
||||
*
|
||||
* @param time
|
||||
* @param time Time to be set in milliseconds.
|
||||
*/
|
||||
setSystemTime(time: number|string|Date): Promise<void>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-core",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "Playwright Component Testing Helpers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,8 +26,8 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next",
|
||||
"playwright-core": "1.45.3",
|
||||
"vite": "^5.2.8",
|
||||
"playwright": "1.45.0-next"
|
||||
"playwright": "1.45.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-react",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "Playwright Component Testing for React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,10 +26,11 @@
|
|||
"./hooks": {
|
||||
"types": "./hooks.d.ts",
|
||||
"default": "./hooks.mjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.45.3",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-react17",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "Playwright Component Testing for React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,10 +26,11 @@
|
|||
"./hooks": {
|
||||
"types": "./hooks.d.ts",
|
||||
"default": "./hooks.mjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.45.3",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-solid",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "Playwright Component Testing for Solid",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,10 +26,11 @@
|
|||
"./hooks": {
|
||||
"types": "./hooks.d.ts",
|
||||
"default": "./hooks.mjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.45.3",
|
||||
"vite-plugin-solid": "^2.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-svelte",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "Playwright Component Testing for Svelte",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,10 +26,11 @@
|
|||
"./hooks": {
|
||||
"types": "./hooks.d.ts",
|
||||
"default": "./hooks.mjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.45.3",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-vue",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "Playwright Component Testing for Vue",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,10 +26,11 @@
|
|||
"./hooks": {
|
||||
"types": "./hooks.d.ts",
|
||||
"default": "./hooks.mjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.45.3",
|
||||
"@vitejs/plugin-vue": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-vue2",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "Playwright Component Testing for Vue2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,10 +26,11 @@
|
|||
"./hooks": {
|
||||
"types": "./hooks.d.ts",
|
||||
"default": "./hooks.mjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.45.0-next",
|
||||
"@playwright/experimental-ct-core": "1.45.3",
|
||||
"@vitejs/plugin-vue2": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-firefox",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "A high-level API to automate Firefox",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -30,6 +30,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/test",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -30,6 +30,6 @@
|
|||
},
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"playwright": "1.45.0-next"
|
||||
"playwright": "1.45.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-webkit",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "A high-level API to automate WebKit",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -30,6 +30,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright",
|
||||
"version": "1.45.0-next",
|
||||
"version": "1.45.3",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
},
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.45.0-next"
|
||||
"playwright-core": "1.45.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
|
|
|
|||
|
|
@ -42,19 +42,3 @@ export function setIsWorkerProcess() {
|
|||
export function isWorkerProcess() {
|
||||
return _isWorkerProcess;
|
||||
}
|
||||
|
||||
export interface TestLifecycleInstrumentation {
|
||||
onTestBegin?(): Promise<void>;
|
||||
onTestFunctionEnd?(): Promise<void>;
|
||||
onTestEnd?(): Promise<void>;
|
||||
}
|
||||
|
||||
let _testLifecycleInstrumentation: TestLifecycleInstrumentation | undefined;
|
||||
|
||||
export function setTestLifecycleInstrumentation(instrumentation: TestLifecycleInstrumentation | undefined) {
|
||||
_testLifecycleInstrumentation = instrumentation;
|
||||
}
|
||||
|
||||
export function testLifecycleInstrumentation() {
|
||||
return _testLifecycleInstrumentation;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,15 @@
|
|||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import type { APIRequestContext, BrowserContext, Browser, BrowserContextOptions, LaunchOptions, Page, Tracing, Video, PageScreenshotOptions } from 'playwright-core';
|
||||
import type { APIRequestContext, BrowserContext, Browser, BrowserContextOptions, LaunchOptions, Page, Tracing, Video } from 'playwright-core';
|
||||
import * as playwrightLibrary from 'playwright-core';
|
||||
import { createGuid, debugMode, addInternalStackPrefix, isString, asLocator, jsonStringifyForceASCII } from 'playwright-core/lib/utils';
|
||||
import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, VideoMode } from '../types/test';
|
||||
import type { TestInfoImpl } from './worker/testInfo';
|
||||
import { rootTestType } from './common/testType';
|
||||
import type { ContextReuseMode } from './common/config';
|
||||
import type { ClientInstrumentation, ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation';
|
||||
import { currentTestInfo, setTestLifecycleInstrumentation, type TestLifecycleInstrumentation } from './common/globals';
|
||||
import { currentTestInfo } from './common/globals';
|
||||
export { expect } from './matchers/expect';
|
||||
export const _baseTest: TestType<{}, {}> = rootTestType.test;
|
||||
|
||||
|
|
@ -44,12 +45,11 @@ if ((process as any)['__pw_initiator__']) {
|
|||
type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
|
||||
_combinedContextOptions: BrowserContextOptions,
|
||||
_setupContextOptions: void;
|
||||
_setupArtifacts: void;
|
||||
_contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
|
||||
};
|
||||
|
||||
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
|
||||
// Same as "playwright", but exposed so that our internal tests can override it.
|
||||
_playwrightImpl: PlaywrightWorkerArgs['playwright'];
|
||||
_browserOptions: LaunchOptions;
|
||||
_optionContextReuseMode: ContextReuseMode,
|
||||
_optionConnectOptions: PlaywrightWorkerOptions['connectOptions'],
|
||||
|
|
@ -59,14 +59,9 @@ type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
|
|||
const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||
defaultBrowserType: ['chromium', { scope: 'worker', option: true }],
|
||||
browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: 'worker', option: true }],
|
||||
_playwrightImpl: [({}, use) => use(require('playwright-core')), { scope: 'worker' }],
|
||||
|
||||
playwright: [async ({ _playwrightImpl, screenshot }, use) => {
|
||||
await connector.setPlaywright(_playwrightImpl, screenshot);
|
||||
await use(_playwrightImpl);
|
||||
await connector.setPlaywright(undefined, screenshot);
|
||||
playwright: [async ({}, use) => {
|
||||
await use(require('playwright-core'));
|
||||
}, { scope: 'worker', _hideStep: true } as any],
|
||||
|
||||
headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: 'worker', option: true }],
|
||||
channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: 'worker', option: true }],
|
||||
launchOptions: [{}, { scope: 'worker', option: true }],
|
||||
|
|
@ -227,7 +222,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
|||
|
||||
_setupContextOptions: [async ({ playwright, _combinedContextOptions, actionTimeout, navigationTimeout, testIdAttribute }, use, testInfo) => {
|
||||
if (testIdAttribute)
|
||||
playwright.selectors.setTestIdAttribute(testIdAttribute);
|
||||
playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
|
||||
testInfo.snapshotSuffix = process.platform;
|
||||
if (debugMode())
|
||||
testInfo.setTimeout(0);
|
||||
|
|
@ -248,6 +243,58 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
|||
}
|
||||
}, { auto: 'all-hooks-included', _title: 'context configuration' } as any],
|
||||
|
||||
_setupArtifacts: [async ({ playwright, screenshot }, use, testInfo) => {
|
||||
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot);
|
||||
await artifactsRecorder.willStartTest(testInfo as TestInfoImpl);
|
||||
const csiListener: ClientInstrumentationListener = {
|
||||
onApiCallBegin: (apiName: string, params: Record<string, any>, frames: StackFrame[], userData: any, out: { stepId?: string }) => {
|
||||
const testInfo = currentTestInfo();
|
||||
if (!testInfo || apiName.includes('setTestIdAttribute'))
|
||||
return { userObject: null };
|
||||
const step = testInfo._addStep({
|
||||
location: frames[0] as any,
|
||||
category: 'pw:api',
|
||||
title: renderApiCall(apiName, params),
|
||||
apiName,
|
||||
params,
|
||||
});
|
||||
userData.userObject = step;
|
||||
out.stepId = step.stepId;
|
||||
},
|
||||
onApiCallEnd: (userData: any, error?: Error) => {
|
||||
const step = userData.userObject;
|
||||
step?.complete({ error });
|
||||
},
|
||||
onWillPause: () => {
|
||||
currentTestInfo()?.setTimeout(0);
|
||||
},
|
||||
runAfterCreateBrowserContext: async (context: BrowserContext) => {
|
||||
await artifactsRecorder?.didCreateBrowserContext(context);
|
||||
const testInfo = currentTestInfo();
|
||||
if (testInfo)
|
||||
attachConnectedHeaderIfNeeded(testInfo, context.browser());
|
||||
},
|
||||
runAfterCreateRequestContext: async (context: APIRequestContext) => {
|
||||
await artifactsRecorder?.didCreateRequestContext(context);
|
||||
},
|
||||
runBeforeCloseBrowserContext: async (context: BrowserContext) => {
|
||||
await artifactsRecorder?.willCloseBrowserContext(context);
|
||||
},
|
||||
runBeforeCloseRequestContext: async (context: APIRequestContext) => {
|
||||
await artifactsRecorder?.willCloseRequestContext(context);
|
||||
},
|
||||
};
|
||||
|
||||
const clientInstrumentation = (playwright as any)._instrumentation as ClientInstrumentation;
|
||||
clientInstrumentation.addListener(csiListener);
|
||||
|
||||
await use();
|
||||
|
||||
clientInstrumentation.removeListener(csiListener);
|
||||
await artifactsRecorder.didFinishTest();
|
||||
|
||||
}, { auto: 'all-hooks-included', _title: 'trace recording' } as any],
|
||||
|
||||
_contextFactory: [async ({ browser, video, _reuseContext }, use, testInfo) => {
|
||||
const testInfoImpl = testInfo as TestInfoImpl;
|
||||
const videoMode = normalizeVideoMode(video);
|
||||
|
|
@ -424,7 +471,7 @@ class ArtifactsRecorder {
|
|||
private _playwright: Playwright;
|
||||
private _artifactsDir: string;
|
||||
private _screenshotMode: ScreenshotMode;
|
||||
private _screenshotOptions: { mode: ScreenshotMode } & Pick<PageScreenshotOptions, 'fullPage' | 'omitBackground'> | undefined;
|
||||
private _screenshotOptions: { mode: ScreenshotMode } & Pick<playwrightLibrary.PageScreenshotOptions, 'fullPage' | 'omitBackground'> | undefined;
|
||||
private _temporaryScreenshots: string[] = [];
|
||||
private _temporaryArtifacts: string[] = [];
|
||||
private _reusedContexts = new Set<BrowserContext>();
|
||||
|
|
@ -449,6 +496,7 @@ class ArtifactsRecorder {
|
|||
|
||||
async willStartTest(testInfo: TestInfoImpl) {
|
||||
this._testInfo = testInfo;
|
||||
testInfo._onDidFinishTestFunction = () => this.didFinishTestFunction();
|
||||
|
||||
// Since beforeAll(s), test and afterAll(s) reuse the same TestInfo, make sure we do not
|
||||
// overwrite previous screenshots.
|
||||
|
|
@ -597,7 +645,7 @@ class ArtifactsRecorder {
|
|||
if ((tracing as any)[this._startedCollectingArtifacts])
|
||||
return;
|
||||
(tracing as any)[this._startedCollectingArtifacts] = true;
|
||||
if (this._testInfo._tracing.traceOptions())
|
||||
if (this._testInfo._tracing.traceOptions() && (tracing as any)[kTracingStarted])
|
||||
await tracing.stopChunk({ path: this._testInfo._tracing.generateNextTraceRecordingPath() });
|
||||
}
|
||||
}
|
||||
|
|
@ -630,101 +678,6 @@ function tracing() {
|
|||
return (test.info() as TestInfoImpl)._tracing;
|
||||
}
|
||||
|
||||
class InstrumentationConnector implements TestLifecycleInstrumentation, ClientInstrumentationListener {
|
||||
private _playwright: PlaywrightWorkerArgs['playwright'] | undefined;
|
||||
private _screenshot: ScreenshotOption = 'off';
|
||||
private _artifactsRecorder: ArtifactsRecorder | undefined;
|
||||
private _testIsRunning = false;
|
||||
|
||||
constructor() {
|
||||
setTestLifecycleInstrumentation(this);
|
||||
}
|
||||
|
||||
async setPlaywright(playwright: PlaywrightWorkerArgs['playwright'] | undefined, screenshot: ScreenshotOption) {
|
||||
if (this._playwright) {
|
||||
if (this._testIsRunning) {
|
||||
// When "playwright" is destroyed during a test, collect artifacts immediately.
|
||||
await this.onTestEnd();
|
||||
}
|
||||
const clientInstrumentation = (this._playwright as any)._instrumentation as ClientInstrumentation;
|
||||
clientInstrumentation.removeListener(this);
|
||||
}
|
||||
this._playwright = playwright;
|
||||
this._screenshot = screenshot;
|
||||
if (this._playwright) {
|
||||
const clientInstrumentation = (this._playwright as any)._instrumentation as ClientInstrumentation;
|
||||
clientInstrumentation.addListener(this);
|
||||
if (this._testIsRunning) {
|
||||
// When "playwright" is created during a test, wire it up immediately.
|
||||
await this.onTestBegin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onTestBegin() {
|
||||
this._testIsRunning = true;
|
||||
if (this._playwright) {
|
||||
this._artifactsRecorder = new ArtifactsRecorder(this._playwright, tracing().artifactsDir(), this._screenshot);
|
||||
await this._artifactsRecorder.willStartTest(currentTestInfo() as TestInfoImpl);
|
||||
}
|
||||
}
|
||||
|
||||
async onTestFunctionEnd() {
|
||||
await this._artifactsRecorder?.didFinishTestFunction();
|
||||
}
|
||||
|
||||
async onTestEnd() {
|
||||
await this._artifactsRecorder?.didFinishTest();
|
||||
this._artifactsRecorder = undefined;
|
||||
this._testIsRunning = false;
|
||||
}
|
||||
|
||||
onApiCallBegin(apiName: string, params: Record<string, any>, frames: StackFrame[], userData: any, out: { stepId?: string }) {
|
||||
const testInfo = currentTestInfo();
|
||||
if (!testInfo || apiName.includes('setTestIdAttribute'))
|
||||
return { userObject: null };
|
||||
const step = testInfo._addStep({
|
||||
location: frames[0] as any,
|
||||
category: 'pw:api',
|
||||
title: renderApiCall(apiName, params),
|
||||
apiName,
|
||||
params,
|
||||
});
|
||||
userData.userObject = step;
|
||||
out.stepId = step.stepId;
|
||||
}
|
||||
|
||||
onApiCallEnd(userData: any, error?: Error) {
|
||||
const step = userData.userObject;
|
||||
step?.complete({ error });
|
||||
}
|
||||
|
||||
onWillPause() {
|
||||
currentTestInfo()?.setTimeout(0);
|
||||
}
|
||||
|
||||
async runAfterCreateBrowserContext(context: BrowserContext) {
|
||||
await this._artifactsRecorder?.didCreateBrowserContext(context);
|
||||
const testInfo = currentTestInfo();
|
||||
if (testInfo)
|
||||
attachConnectedHeaderIfNeeded(testInfo, context.browser());
|
||||
}
|
||||
|
||||
async runAfterCreateRequestContext(context: APIRequestContext) {
|
||||
await this._artifactsRecorder?.didCreateRequestContext(context);
|
||||
}
|
||||
|
||||
async runBeforeCloseBrowserContext(context: BrowserContext) {
|
||||
await this._artifactsRecorder?.willCloseBrowserContext(context);
|
||||
}
|
||||
|
||||
async runBeforeCloseRequestContext(context: APIRequestContext) {
|
||||
await this._artifactsRecorder?.willCloseRequestContext(context);
|
||||
}
|
||||
}
|
||||
|
||||
const connector = new InstrumentationConnector();
|
||||
|
||||
export const test = _baseTest.extend<TestFixtures, WorkerFixtures>(playwrightFixtures);
|
||||
|
||||
export { defineConfig } from './common/configLoader';
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ export class TestInfoImpl implements TestInfo {
|
|||
readonly _projectInternal: FullProjectInternal;
|
||||
readonly _configInternal: FullConfigInternal;
|
||||
private readonly _steps: TestStepInternal[] = [];
|
||||
_onDidFinishTestFunction: (() => Promise<void>) | undefined;
|
||||
private readonly _stages: TestStage[] = [];
|
||||
_hasNonRetriableError = false;
|
||||
_hasUnhandledError = false;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||
import { debugTest, relativeFilePath, serializeError } from '../util';
|
||||
import { type TestBeginPayload, type TestEndPayload, type RunPayload, type DonePayload, type WorkerInitParams, type TeardownErrorsPayload, stdioChunkToParams } from '../common/ipc';
|
||||
import { setCurrentTestInfo, setIsWorkerProcess, testLifecycleInstrumentation } from '../common/globals';
|
||||
import { setCurrentTestInfo, setIsWorkerProcess } from '../common/globals';
|
||||
import { deserializeConfig } from '../common/configLoader';
|
||||
import type { Suite, TestCase } from '../common/test';
|
||||
import type { Annotation, FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||
|
|
@ -304,11 +304,10 @@ export class WorkerMain extends ProcessRunner {
|
|||
if (this._lastRunningTests.length > 10)
|
||||
this._lastRunningTests.shift();
|
||||
let shouldRunAfterEachHooks = false;
|
||||
const tracingSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
||||
|
||||
testInfo._allowSkips = true;
|
||||
await testInfo._runAsStage({ title: 'setup and test' }, async () => {
|
||||
await testInfo._runAsStage({ title: 'start tracing', runnable: { type: 'test', slot: tracingSlot } }, async () => {
|
||||
await testInfo._runAsStage({ title: 'start tracing', runnable: { type: 'test' } }, async () => {
|
||||
// Ideally, "trace" would be an config-level option belonging to the
|
||||
// test runner instead of a fixture belonging to Playwright.
|
||||
// However, for backwards compatibility, we have to read it from a fixture today.
|
||||
|
|
@ -319,7 +318,6 @@ export class WorkerMain extends ProcessRunner {
|
|||
if (typeof traceFixtureRegistration.fn === 'function')
|
||||
throw new Error(`"trace" option cannot be a function`);
|
||||
await testInfo._tracing.startIfNeeded(traceFixtureRegistration.fn);
|
||||
await testLifecycleInstrumentation()?.onTestBegin?.();
|
||||
});
|
||||
|
||||
if (this._isStopped || isSkipped) {
|
||||
|
|
@ -374,10 +372,10 @@ export class WorkerMain extends ProcessRunner {
|
|||
|
||||
try {
|
||||
// Run "immediately upon test function finish" callback.
|
||||
await testInfo._runAsStage({ title: 'on-test-function-finish', runnable: { type: 'test', slot: tracingSlot } }, async () => {
|
||||
await testLifecycleInstrumentation()?.onTestFunctionEnd?.();
|
||||
});
|
||||
await testInfo._runAsStage({ title: 'on-test-function-finish', runnable: { type: 'test', slot: afterHooksSlot } }, async () => testInfo._onDidFinishTestFunction?.());
|
||||
} catch (error) {
|
||||
if (error instanceof TimeoutManagerError)
|
||||
didTimeoutInAfterHooks = true;
|
||||
firstAfterHooksError = firstAfterHooksError ?? error;
|
||||
}
|
||||
|
||||
|
|
@ -460,8 +458,8 @@ export class WorkerMain extends ProcessRunner {
|
|||
}).catch(() => {}); // Ignore the top-level error, it is already inside TestInfo.errors.
|
||||
}
|
||||
|
||||
const tracingSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
||||
await testInfo._runAsStage({ title: 'stop tracing', runnable: { type: 'test', slot: tracingSlot } }, async () => {
|
||||
await testLifecycleInstrumentation()?.onTestEnd?.();
|
||||
await testInfo._tracing.stopIfNeeded();
|
||||
}).catch(() => {}); // Ignore the top-level error, it is already inside TestInfo.errors.
|
||||
|
||||
|
|
@ -570,6 +568,9 @@ export class WorkerMain extends ProcessRunner {
|
|||
if (error instanceof TimeoutManagerError)
|
||||
throw error;
|
||||
firstError = firstError ?? error;
|
||||
// Skip in modifier prevents others from running.
|
||||
if (error instanceof SkipError)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (firstError)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { unwrapPopoutUrl } from './snapshotRenderer';
|
|||
import { SnapshotServer } from './snapshotServer';
|
||||
import { TraceModel } from './traceModel';
|
||||
import { FetchTraceModelBackend, ZipTraceModelBackend } from './traceModelBackends';
|
||||
import { TraceVersionError } from './traceModernizer';
|
||||
|
||||
// @ts-ignore
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
|
@ -57,6 +58,8 @@ async function loadTrace(traceUrl: string, traceFileName: string | null, clientI
|
|||
console.error(error);
|
||||
if (error?.message?.includes('Cannot find .trace file') && await traceModel.hasEntry('index.html'))
|
||||
throw new Error('Could not load trace. Did you upload a Playwright HTML report instead? Make sure to extract the archive first and then double-click the index.html file or put it on a web server.');
|
||||
if (error instanceof TraceVersionError)
|
||||
throw new Error(`Could not load trace from ${traceFileName || traceUrl}. ${error.message}`);
|
||||
if (traceFileName)
|
||||
throw new Error(`Could not load trace from ${traceFileName}. Make sure to upload a valid Playwright trace.`);
|
||||
throw new Error(`Could not load trace from ${traceUrl}. Make sure a valid Playwright Trace is accessible over this url.`);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,15 @@ import type * as traceV6 from './versions/traceV6';
|
|||
import type { ActionEntry, ContextEntry, PageEntry } from './entries';
|
||||
import type { SnapshotStorage } from './snapshotStorage';
|
||||
|
||||
export class TraceVersionError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'TraceVersionError';
|
||||
}
|
||||
}
|
||||
|
||||
const latestVersion: trace.VERSION = 7;
|
||||
|
||||
export class TraceModernizer {
|
||||
private _contextEntry: ContextEntry;
|
||||
private _snapshotStorage: SnapshotStorage;
|
||||
|
|
@ -71,6 +80,8 @@ export class TraceModernizer {
|
|||
const contextEntry = this._contextEntry;
|
||||
switch (event.type) {
|
||||
case 'context-options': {
|
||||
if (event.version > latestVersion)
|
||||
throw new TraceVersionError('The trace was created by a newer version of Playwright and is not supported by this version of the viewer. Please use latest Playwright to open the trace.');
|
||||
this._version = event.version;
|
||||
contextEntry.origin = event.origin;
|
||||
contextEntry.browserName = event.browserName;
|
||||
|
|
@ -181,9 +192,8 @@ export class TraceModernizer {
|
|||
let version = this._version || event.version;
|
||||
if (version === undefined)
|
||||
return [event];
|
||||
const lastVersion: trace.VERSION = 7;
|
||||
let events = [event];
|
||||
for (; version < lastVersion; ++version)
|
||||
for (; version < latestVersion; ++version)
|
||||
events = (this as any)[`_modernize_${version}_to_${version + 1}`].call(this, events);
|
||||
return events;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,8 +183,8 @@ function mergeActionsAndUpdateTiming(contexts: ContextEntry[]) {
|
|||
if (traceFileToContexts.size > 1)
|
||||
makeCallIdsUniqueAcrossTraceFiles(contexts, ++traceFileId);
|
||||
// Align action times across runner and library contexts within each trace file.
|
||||
const map = mergeActionsAndUpdateTimingSameTrace(contexts);
|
||||
result.push(...map.values());
|
||||
const actions = mergeActionsAndUpdateTimingSameTrace(contexts);
|
||||
result.push(...actions);
|
||||
}
|
||||
result.sort((a1, a2) => {
|
||||
if (a2.parentId === a1.callId)
|
||||
|
|
@ -211,12 +211,19 @@ function makeCallIdsUniqueAcrossTraceFiles(contexts: ContextEntry[], traceFileId
|
|||
}
|
||||
}
|
||||
|
||||
function mergeActionsAndUpdateTimingSameTrace(contexts: ContextEntry[]) {
|
||||
function mergeActionsAndUpdateTimingSameTrace(contexts: ContextEntry[]): ActionTraceEventInContext[] {
|
||||
const map = new Map<string, ActionTraceEventInContext>();
|
||||
|
||||
const libraryContexts = contexts.filter(context => context.origin === 'library');
|
||||
const testRunnerContexts = contexts.filter(context => context.origin === 'testRunner');
|
||||
|
||||
// With library-only or test-runner-only traces there is nothing to match.
|
||||
if (!testRunnerContexts.length || !libraryContexts.length) {
|
||||
return contexts.map(context => {
|
||||
return context.actions.map(action => ({ ...action, context }));
|
||||
}).flat();
|
||||
}
|
||||
|
||||
// Library actions are replaced with corresponding test runner steps. Matching with
|
||||
// the test runner steps enables us to find parent steps.
|
||||
// - In the newer versions the actions are matched by explicit step id stored in the
|
||||
|
|
@ -264,7 +271,7 @@ function mergeActionsAndUpdateTimingSameTrace(contexts: ContextEntry[]) {
|
|||
map.set(key, { ...action, context });
|
||||
}
|
||||
}
|
||||
return map;
|
||||
return [...map.values()];
|
||||
}
|
||||
|
||||
function adjustMonotonicTime(contexts: ContextEntry[], monotonicTimeDelta: number) {
|
||||
|
|
|
|||
BIN
tests/assets/trace-from-the-future.zip
Normal file
BIN
tests/assets/trace-from-the-future.zip
Normal file
Binary file not shown.
BIN
tests/assets/trace-library-1.46.zip
Normal file
BIN
tests/assets/trace-library-1.46.zip
Normal file
Binary file not shown.
|
|
@ -30,12 +30,11 @@ export type TestModeTestFixtures = {
|
|||
export type TestModeWorkerFixtures = {
|
||||
toImplInWorkerScope: (rpcObject?: any) => any;
|
||||
playwright: typeof import('@playwright/test');
|
||||
_playwrightImpl: typeof import('@playwright/test');
|
||||
};
|
||||
|
||||
export const testModeTest = test.extend<TestModeTestFixtures, TestModeWorkerOptions & TestModeWorkerFixtures>({
|
||||
mode: ['default', { scope: 'worker', option: true }],
|
||||
_playwrightImpl: [async ({ mode }, run) => {
|
||||
playwright: [async ({ mode }, run) => {
|
||||
const testMode = {
|
||||
'default': new DefaultTestMode(),
|
||||
'service': new DefaultTestMode(),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
import { test } from './npmTest';
|
||||
import fs from 'fs';
|
||||
import { expect } from 'packages/playwright-test';
|
||||
import path from 'path';
|
||||
|
||||
test('electron should work', async ({ exec, tsc, writeFiles }) => {
|
||||
|
|
@ -39,3 +40,46 @@ test('electron should work with special characters in path', async ({ exec, tmpW
|
|||
cwd: path.join(folderName)
|
||||
});
|
||||
});
|
||||
|
||||
test('should work when wrapped inside @playwright/test and trace is enabled', async ({ exec, tmpWorkspace, writeFiles }) => {
|
||||
await exec('npm i -D @playwright/test electron@31');
|
||||
await writeFiles({
|
||||
'electron-with-tracing.spec.ts': `
|
||||
import { test, expect, _electron } from '@playwright/test';
|
||||
|
||||
test('should work', async ({ trace }) => {
|
||||
const electronApp = await _electron.launch({ args: [${JSON.stringify(path.join(__dirname, '../electron/electron-window-app.js'))}] });
|
||||
|
||||
const window = await electronApp.firstWindow();
|
||||
if (trace)
|
||||
await window.context().tracing.start({ screenshots: true, snapshots: true });
|
||||
|
||||
await window.goto('data:text/html,<title>Playwright</title><h1>Playwright</h1>');
|
||||
await expect(window).toHaveTitle(/Playwright/);
|
||||
await expect(window.getByRole('heading')).toHaveText('Playwright');
|
||||
|
||||
const path = test.info().outputPath('electron-trace.zip');
|
||||
if (trace) {
|
||||
await window.context().tracing.stop({ path });
|
||||
test.info().attachments.push({ name: 'trace', path, contentType: 'application/zip' });
|
||||
}
|
||||
await electronApp.close();
|
||||
});
|
||||
`,
|
||||
});
|
||||
const jsonOutputName = test.info().outputPath('report.json');
|
||||
await exec('npx playwright test --trace=on --reporter=json electron-with-tracing.spec.ts', {
|
||||
env: { PLAYWRIGHT_JSON_OUTPUT_NAME: jsonOutputName }
|
||||
});
|
||||
const traces = [
|
||||
// our actual trace.
|
||||
path.join(tmpWorkspace, 'test-results', 'electron-with-tracing-should-work', 'electron-trace.zip'),
|
||||
// contains the expect() calls
|
||||
path.join(tmpWorkspace, 'test-results', 'electron-with-tracing-should-work', 'trace.zip'),
|
||||
];
|
||||
for (const trace of traces)
|
||||
expect(fs.existsSync(trace)).toBe(true);
|
||||
const report = JSON.parse(fs.readFileSync(jsonOutputName, 'utf-8'));
|
||||
expect(new Set(['trace'])).toEqual(new Set(report.suites[0].specs[0].tests[0].results[0].attachments.map(a => a.name)));
|
||||
expect(new Set(traces.map(p => fs.realpathSync(p)))).toEqual(new Set(report.suites[0].specs[0].tests[0].results[0].attachments.map(a => a.path)));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -435,7 +435,7 @@ it('should return error with wrong credentials', async ({ context, server }) =>
|
|||
expect(response2.status()).toBe(401);
|
||||
});
|
||||
|
||||
it('should support HTTPCredentials.sendImmediately for newContext', async ({ contextFactory, server }) => {
|
||||
it('should support HTTPCredentials.send for newContext', async ({ contextFactory, server }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30534' });
|
||||
const context = await contextFactory({
|
||||
httpCredentials: { username: 'user', password: 'pass', origin: server.PREFIX.toUpperCase(), send: 'always' }
|
||||
|
|
@ -459,7 +459,7 @@ it('should support HTTPCredentials.sendImmediately for newContext', async ({ con
|
|||
}
|
||||
});
|
||||
|
||||
it('should support HTTPCredentials.sendImmediately for browser.newPage', async ({ contextFactory, server, browser }) => {
|
||||
it('should support HTTPCredentials.send for browser.newPage', async ({ contextFactory, server, browser }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30534' });
|
||||
const page = await browser.newPage({
|
||||
httpCredentials: { username: 'user', password: 'pass', origin: server.PREFIX.toUpperCase(), send: 'always' }
|
||||
|
|
|
|||
|
|
@ -252,6 +252,19 @@ test('should reset tracing', async ({ reusedContext, trace }, testInfo) => {
|
|||
expect(error.message).toContain('Must start tracing before stopping');
|
||||
});
|
||||
|
||||
test('should work with clock emulation', async ({ reusedContext, trace }, testInfo) => {
|
||||
let context = await reusedContext();
|
||||
|
||||
let page = await context.newPage();
|
||||
await page.clock.setFixedTime(new Date('2020-01-01T00:00:00.000Z'));
|
||||
expect(await page.evaluate('new Date().toISOString()')).toBe('2020-01-01T00:00:00.000Z');
|
||||
|
||||
context = await reusedContext();
|
||||
page = context.pages()[0];
|
||||
await page.clock.setFixedTime(new Date('2020-01-01T00:00:00Z'));
|
||||
expect(await page.evaluate('new Date().toISOString()')).toBe('2020-01-01T00:00:00.000Z');
|
||||
});
|
||||
|
||||
test('should continue issuing events after closing the reused page', async ({ reusedContext, server }) => {
|
||||
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/24574' });
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ it('should play audio @smoke', async ({ page, server, browserName, platform }) =
|
|||
});
|
||||
|
||||
it('should support webgl @smoke', async ({ page, browserName, platform }) => {
|
||||
it.fixme(browserName === 'chromium' && platform === 'darwin' && os.arch() === 'arm64', 'SwiftShader is not available on macOS-arm64 - https://github.com/microsoft/playwright/issues/28216');
|
||||
const hasWebGL = await page.evaluate(() => {
|
||||
const canvas = document.createElement('canvas');
|
||||
return !!canvas.getContext('webgl');
|
||||
|
|
@ -118,7 +119,10 @@ it('should support webgl @smoke', async ({ page, browserName, platform }) => {
|
|||
});
|
||||
|
||||
it('should support webgl 2 @smoke', async ({ page, browserName, headless, isWindows, platform }) => {
|
||||
it.skip(browserName === 'webkit', 'WebKit doesn\'t have webgl2 enabled yet upstream.');
|
||||
it.fixme(browserName === 'firefox' && isWindows);
|
||||
it.fixme(browserName === 'chromium' && !headless, 'chromium doesn\'t like webgl2 when running under xvfb');
|
||||
it.fixme(browserName === 'chromium' && platform === 'darwin' && os.arch() === 'arm64', 'SwiftShader is not available on macOS-arm64 - https://github.com/microsoft/playwright/issues/28216');
|
||||
|
||||
const hasWebGL2 = await page.evaluate(() => {
|
||||
const canvas = document.createElement('canvas');
|
||||
|
|
|
|||
68
tests/library/chromium/disable-web-security.spec.ts
Normal file
68
tests/library/chromium/disable-web-security.spec.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* Copyright 2018 Google Inc. All rights reserved.
|
||||
* Modifications copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { contextTest as it, expect } from '../../config/browserTest';
|
||||
|
||||
it.use({
|
||||
launchOptions: async ({ launchOptions }, use) => {
|
||||
await use({ ...launchOptions, args: ['--disable-web-security'] });
|
||||
}
|
||||
});
|
||||
|
||||
it('test utility world in popup w/ --disable-web-security', async ({ page, server }) => {
|
||||
server.setRoute('/main.html', (req, res) => {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.end(`<a href="${server.PREFIX}/target.html" target="_blank">Click me</a>`);
|
||||
});
|
||||
server.setRoute('/target.html', (req, res) => {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.end(`<html></html>`);
|
||||
});
|
||||
|
||||
await page.goto(server.PREFIX + '/main.html');
|
||||
const page1Promise = page.context().waitForEvent('page');
|
||||
await page.getByRole('link', { name: 'Click me' }).click();
|
||||
const page1 = await page1Promise;
|
||||
await expect(page1).toHaveURL(/target/);
|
||||
});
|
||||
|
||||
it('test init script w/ --disable-web-security', async ({ page, server }) => {
|
||||
server.setRoute('/main.html', (req, res) => {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.end(`<a href="${server.PREFIX}/target.html" target="_blank">Click me</a>`);
|
||||
});
|
||||
server.setRoute('/target.html', (req, res) => {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.end(`<html></html>`);
|
||||
});
|
||||
|
||||
await page.context().addInitScript('window.injected = 123');
|
||||
await page.goto(server.PREFIX + '/main.html');
|
||||
const page1Promise = page.context().waitForEvent('page');
|
||||
await page.getByRole('link', { name: 'Click me' }).click();
|
||||
const page1 = await page1Promise;
|
||||
const value = await page1.evaluate('window.injected');
|
||||
expect(value).toBe(123);
|
||||
});
|
||||
|
|
@ -154,7 +154,7 @@ it('should support WWW-Authenticate: Basic', async ({ playwright, server }) => {
|
|||
expect(credentials).toBe('user:pass');
|
||||
});
|
||||
|
||||
it('should support HTTPCredentials.sendImmediately', async ({ playwright, server }) => {
|
||||
it('should support HTTPCredentials.send', async ({ playwright, server }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30534' });
|
||||
const request = await playwright.request.newContext({
|
||||
httpCredentials: { username: 'user', password: 'pass', origin: server.PREFIX.toUpperCase(), send: 'always' }
|
||||
|
|
|
|||
|
|
@ -120,6 +120,11 @@ test('should open simple trace viewer', async ({ showTraceViewer }) => {
|
|||
]);
|
||||
});
|
||||
|
||||
test('should complain about newer version of trace in old viewer', async ({ showTraceViewer, asset }, testInfo) => {
|
||||
const traceViewer = await showTraceViewer([asset('trace-from-the-future.zip')]);
|
||||
await expect(traceViewer.page.getByText('The trace was created by a newer version of Playwright and is not supported by this version of the viewer.')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should contain action info', async ({ showTraceViewer }) => {
|
||||
const traceViewer = await showTraceViewer([traceFile]);
|
||||
await traceViewer.selectAction('locator.click');
|
||||
|
|
@ -1236,3 +1241,14 @@ test('should open snapshot in new browser context', async ({ browser, page, runA
|
|||
await expect(newPage.getByText('hello')).toBeVisible();
|
||||
await newPage.close();
|
||||
});
|
||||
|
||||
test('should show similar actions from library-only trace', async ({ showTraceViewer, asset }) => {
|
||||
const traceViewer = await showTraceViewer([asset('trace-library-1.46.zip')]);
|
||||
await expect(traceViewer.actionTitles).toHaveText([
|
||||
/page.setContent/,
|
||||
/locator.getAttributelocator\('div'\)/,
|
||||
/locator.isVisiblelocator\('div'\)/,
|
||||
/locator.getAttributelocator\('div'\)/,
|
||||
/locator.isVisiblelocator\('div'\)/,
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -245,6 +245,11 @@ it.describe('stubTimers', () => {
|
|||
expect(await page.evaluate(() => Date.now())).toBe(1400);
|
||||
});
|
||||
|
||||
it('should throw for invalid date', async ({ page }) => {
|
||||
await expect(page.clock.setSystemTime(new Date('invalid'))).rejects.toThrow('Invalid date: Invalid Date');
|
||||
await expect(page.clock.setSystemTime('invalid')).rejects.toThrow('clock.setSystemTime: Invalid date: invalid');
|
||||
});
|
||||
|
||||
it('replaces global setTimeout', async ({ page, calls }) => {
|
||||
await page.evaluate(async () => {
|
||||
setTimeout(window.stub, 1000);
|
||||
|
|
|
|||
|
|
@ -151,8 +151,10 @@ test('should work with screenshot: on', async ({ runInlineTest }, testInfo) => {
|
|||
' test-finished-1.png',
|
||||
'artifacts-shared-shared-failing',
|
||||
' test-failed-1.png',
|
||||
' test-failed-2.png',
|
||||
'artifacts-shared-shared-passing',
|
||||
' test-finished-1.png',
|
||||
' test-finished-2.png',
|
||||
'artifacts-two-contexts',
|
||||
' test-finished-1.png',
|
||||
' test-finished-2.png',
|
||||
|
|
@ -183,6 +185,7 @@ test('should work with screenshot: only-on-failure', async ({ runInlineTest }, t
|
|||
' test-failed-1.png',
|
||||
'artifacts-shared-shared-failing',
|
||||
' test-failed-1.png',
|
||||
' test-failed-2.png',
|
||||
'artifacts-two-contexts-failing',
|
||||
' test-failed-1.png',
|
||||
' test-failed-2.png',
|
||||
|
|
|
|||
|
|
@ -569,19 +569,14 @@ test('should opt out of attachments', async ({ runInlineTest, server }, testInfo
|
|||
expect([...trace.resources.keys()].filter(f => f.startsWith('resources/'))).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should record with custom page fixture that closes the context', async ({ runInlineTest }, testInfo) => {
|
||||
// Note that original issue did not close the context, but we do not support such usecase.
|
||||
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23220' });
|
||||
|
||||
test('should record with custom page fixture', async ({ runInlineTest }, testInfo) => {
|
||||
const result = await runInlineTest({
|
||||
'a.spec.ts': `
|
||||
import { test as base, expect } from '@playwright/test';
|
||||
|
||||
const test = base.extend({
|
||||
myPage: async ({ browser }, use) => {
|
||||
const page = await browser.newPage();
|
||||
await use(page);
|
||||
await page.close();
|
||||
await use(await browser.newPage());
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -1118,120 +1113,68 @@ test('trace:retain-on-first-failure should create trace if request context is di
|
|||
expect(result.failed).toBe(1);
|
||||
});
|
||||
|
||||
test('should record trace in workerStorageState', async ({ runInlineTest }) => {
|
||||
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30287' });
|
||||
|
||||
test('should not corrupt actions when no library trace is present', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.spec.ts': `
|
||||
import { test as base, expect } from '@playwright/test';
|
||||
const test = base.extend({
|
||||
storageState: ({ workerStorageState }, use) => use(workerStorageState),
|
||||
workerStorageState: [async ({ browser }, use) => {
|
||||
const page = await browser.newPage({ storageState: undefined });
|
||||
await page.setContent('<div>hello</div>');
|
||||
await page.close();
|
||||
await use(undefined);
|
||||
}, { scope: 'worker' }],
|
||||
})
|
||||
test('pass', async ({ page }) => {
|
||||
await page.goto('data:text/html,<div>hi</div>');
|
||||
foo: async ({}, use) => {
|
||||
expect(1).toBe(1);
|
||||
await use();
|
||||
expect(2).toBe(2);
|
||||
},
|
||||
});
|
||||
test('fail', async ({ foo }) => {
|
||||
expect(1).toBe(2);
|
||||
});
|
||||
`,
|
||||
}, { trace: 'on' });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
|
||||
const tracePath = test.info().outputPath('test-results', 'a-pass', 'trace.zip');
|
||||
const trace = await parseTrace(tracePath);
|
||||
expect(trace.actionTree).toEqual([
|
||||
'Before Hooks',
|
||||
' fixture: browser',
|
||||
' browserType.launch',
|
||||
' fixture: workerStorageState',
|
||||
' browser.newPage',
|
||||
' page.setContent',
|
||||
' page.close',
|
||||
' fixture: context',
|
||||
' browser.newContext',
|
||||
' fixture: page',
|
||||
' browserContext.newPage',
|
||||
'page.goto',
|
||||
'After Hooks',
|
||||
' fixture: page',
|
||||
' fixture: context',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should record trace after fixture teardown timeout', async ({ runInlineTest }) => {
|
||||
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30718' });
|
||||
|
||||
const result = await runInlineTest({
|
||||
'a.spec.ts': `
|
||||
import { test as base, expect } from '@playwright/test';
|
||||
const test = base.extend({
|
||||
fixture: async ({}, use) => {
|
||||
await use('foo');
|
||||
await new Promise(() => {});
|
||||
},
|
||||
})
|
||||
test('fails', async ({ fixture, page }) => {
|
||||
await page.evaluate(() => console.log('from the page'));
|
||||
});
|
||||
`,
|
||||
}, { trace: 'on', timeout: '4000' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
|
||||
const tracePath = test.info().outputPath('test-results', 'a-fails', 'trace.zip');
|
||||
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip');
|
||||
const trace = await parseTrace(tracePath);
|
||||
expect(trace.actionTree).toEqual([
|
||||
'Before Hooks',
|
||||
' fixture: foo',
|
||||
' expect.toBe',
|
||||
'expect.toBe',
|
||||
'After Hooks',
|
||||
' fixture: foo',
|
||||
' expect.toBe',
|
||||
'Worker Cleanup',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should record trace for manually created context in a failed test', async ({ runInlineTest }) => {
|
||||
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31541' });
|
||||
|
||||
const result = await runInlineTest({
|
||||
'a.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('fail', async ({ browser }) => {
|
||||
const page = await browser.newPage();
|
||||
await page.setContent('<script>console.log("from the page");</script>');
|
||||
expect(1).toBe(2);
|
||||
});
|
||||
`,
|
||||
}, { trace: 'on' });
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
|
||||
const tracePath = test.info().outputPath('test-results', 'a-fail', 'trace.zip');
|
||||
const trace = await parseTrace(tracePath);
|
||||
expect(trace.actionTree).toEqual([
|
||||
'Before Hooks',
|
||||
' fixture: fixture',
|
||||
' fixture: browser',
|
||||
' browserType.launch',
|
||||
' fixture: context',
|
||||
' browser.newContext',
|
||||
' fixture: page',
|
||||
' browserContext.newPage',
|
||||
'page.evaluate',
|
||||
'browser.newPage',
|
||||
'page.setContent',
|
||||
'expect.toBe',
|
||||
'After Hooks',
|
||||
' fixture: page',
|
||||
' fixture: context',
|
||||
' fixture: fixture',
|
||||
'Worker Cleanup',
|
||||
' fixture: browser',
|
||||
]);
|
||||
// Check console events to make sure that library trace is recorded.
|
||||
expect(trace.events).toContainEqual(expect.objectContaining({ type: 'console', text: 'from the page' }));
|
||||
});
|
||||
|
||||
test('should take a screenshot-on-failure in workerStorageState', async ({ runInlineTest }) => {
|
||||
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30959' });
|
||||
|
||||
const result = await runInlineTest({
|
||||
'playwright.config.ts': `
|
||||
export default {
|
||||
use: {
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
};
|
||||
`,
|
||||
'a.spec.ts': `
|
||||
import { test as base, expect } from '@playwright/test';
|
||||
const test = base.extend({
|
||||
storageState: ({ workerStorageState }, use) => use(workerStorageState),
|
||||
workerStorageState: [async ({ browser }, use) => {
|
||||
const page = await browser.newPage({ storageState: undefined });
|
||||
await page.setContent('hello world!');
|
||||
throw new Error('Failed!');
|
||||
await use(undefined);
|
||||
}, { scope: 'worker' }],
|
||||
})
|
||||
test('fail', async ({ page }) => {
|
||||
});
|
||||
`,
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.failed).toBe(1);
|
||||
expect(fs.existsSync(test.info().outputPath('test-results', 'a-fail', 'test-failed-1.png'))).toBeTruthy();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -690,3 +690,25 @@ test('static modifiers should be added in serial mode', async ({ runInlineTest }
|
|||
expect(result.report.suites[0].specs[2].tests[0].annotations).toEqual([{ type: 'skip' }]);
|
||||
expect(result.report.suites[0].specs[3].tests[0].annotations).toEqual([]);
|
||||
});
|
||||
|
||||
test('should skip beforeEach hooks upon modifiers', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
import { test } from '@playwright/test';
|
||||
test('top', () => {});
|
||||
|
||||
test.describe(() => {
|
||||
test.skip(({ viewport }) => true);
|
||||
test.beforeEach(() => { throw new Error(); });
|
||||
|
||||
test.describe(() => {
|
||||
test.beforeEach(() => { throw new Error(); });
|
||||
test('test', () => {});
|
||||
});
|
||||
});
|
||||
`,
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.skipped).toBe(1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ class Documentation {
|
|||
renderLinksInNodes(nodes, classOrMember) {
|
||||
if (classOrMember instanceof Member) {
|
||||
classOrMember.discouraged = classOrMember.discouraged ? this.renderLinksInText(classOrMember.discouraged, classOrMember) : undefined;
|
||||
classOrMember.deprecated = classOrMember.deprecated ? this.renderLinksInText(classOrMember.deprecated, classOrMember) : undefined
|
||||
classOrMember.deprecated = classOrMember.deprecated ? this.renderLinksInText(classOrMember.deprecated, classOrMember) : undefined;
|
||||
}
|
||||
md.visitAll(nodes, node => {
|
||||
if (!node.text)
|
||||
|
|
@ -208,7 +208,7 @@ class Documentation {
|
|||
}
|
||||
}
|
||||
|
||||
class Class {
|
||||
class Class {
|
||||
/**
|
||||
* @param {Metainfo} metainfo
|
||||
* @param {string} name
|
||||
|
|
@ -322,7 +322,7 @@ class Documentation {
|
|||
for (const e of this.eventsArray)
|
||||
e.visit(visitor);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class Member {
|
||||
/**
|
||||
|
|
@ -345,7 +345,7 @@ class Member {
|
|||
this.spec = spec;
|
||||
this.argsArray = argsArray;
|
||||
this.required = required;
|
||||
this.comment = '';
|
||||
this.comment = '';
|
||||
/** @type {!Map<string, !Member>} */
|
||||
this.args = new Map();
|
||||
this.index();
|
||||
|
|
@ -473,8 +473,10 @@ class Member {
|
|||
this.type.visit(visitor);
|
||||
for (const arg of this.argsArray)
|
||||
arg.visit(visitor);
|
||||
for (const lang in this.langs.overrides || {})
|
||||
this.langs.overrides?.[lang].visit(visitor);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class Type {
|
||||
/**
|
||||
|
|
@ -509,9 +511,9 @@ class Type {
|
|||
* @return {Type}
|
||||
*/
|
||||
static fromParsedType(parsedType, inUnion = false) {
|
||||
if (!inUnion && !parsedType.unionName && isStringUnion(parsedType) ) {
|
||||
if (!inUnion && !parsedType.unionName && isStringUnion(parsedType))
|
||||
throw new Error('Enum must have a name:\n' + JSON.stringify(parsedType, null, 2));
|
||||
}
|
||||
|
||||
|
||||
if (!inUnion && (parsedType.union || parsedType.unionName)) {
|
||||
const type = new Type(parsedType.unionName || '');
|
||||
|
|
@ -556,15 +558,15 @@ class Type {
|
|||
/** @type {Member[] | undefined} */
|
||||
this.properties = this.name === 'Object' ? properties : undefined;
|
||||
/** @type {Type[] | undefined} */
|
||||
this.union;
|
||||
this.union = undefined;
|
||||
/** @type {Type[] | undefined} */
|
||||
this.args;
|
||||
this.args = undefined;
|
||||
/** @type {Type | undefined} */
|
||||
this.returnType;
|
||||
this.returnType = undefined;
|
||||
/** @type {Type[] | undefined} */
|
||||
this.templates;
|
||||
this.templates = undefined;
|
||||
/** @type {string | undefined} */
|
||||
this.expression;
|
||||
this.expression = undefined;
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
|
|
@ -645,7 +647,7 @@ class Type {
|
|||
if (this.returnType)
|
||||
this.returnType._collectAllTypes(result);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ParsedType | null} type
|
||||
|
|
@ -866,6 +868,7 @@ function csharpOptionOverloadSuffix(option, type) {
|
|||
case 'Buffer': return 'Byte';
|
||||
case 'Serializable': return 'Object';
|
||||
case 'int': return 'Int';
|
||||
case 'long': return 'Int64';
|
||||
case 'Date': return 'Date';
|
||||
}
|
||||
throw new Error(`CSharp option "${option}" has unsupported type overload "${type}"`);
|
||||
|
|
@ -930,7 +933,7 @@ function processCodeGroups(spec, language, transformer) {
|
|||
* @param {string} codeLang
|
||||
* @return {{ highlighter: string, language: string|undefined, codeGroup: string|undefined}}
|
||||
*/
|
||||
function parseCodeLang(codeLang) {
|
||||
function parseCodeLang(codeLang) {
|
||||
if (codeLang === 'python async')
|
||||
return { highlighter: 'py', codeGroup: 'python-async', language: 'python' };
|
||||
if (codeLang === 'python sync')
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
// @ts-check
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const Documentation = require('./documentation');
|
||||
const { parseApi } = require('./api_parser');
|
||||
const PROJECT_DIR = path.join(__dirname, '..', '..');
|
||||
|
|
@ -73,7 +72,7 @@ function serializeMember(member) {
|
|||
sanitize(result);
|
||||
result.args = member.argsArray.map(serializeProperty);
|
||||
if (member.type)
|
||||
result.type = serializeType(member.type)
|
||||
result.type = serializeType(member.type);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +80,7 @@ function serializeProperty(arg) {
|
|||
const result = { ...arg };
|
||||
sanitize(result);
|
||||
if (arg.type)
|
||||
result.type = serializeType(arg.type, arg.name === 'options')
|
||||
result.type = serializeType(arg.type, arg.name === 'options');
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ classNameMap.set('boolean', 'bool');
|
|||
classNameMap.set('any', 'object');
|
||||
classNameMap.set('Buffer', 'byte[]');
|
||||
classNameMap.set('path', 'string');
|
||||
classNameMap.set('Date', 'DateTime');
|
||||
classNameMap.set('URL', 'string');
|
||||
classNameMap.set('RegExp', 'Regex');
|
||||
classNameMap.set('Readable', 'Stream');
|
||||
|
|
@ -829,7 +830,7 @@ function translateType(type, parent, generateNameCallback = t => t.name, optiona
|
|||
* @param {Documentation.Type} type
|
||||
*/
|
||||
function registerModelType(typeName, type) {
|
||||
if (['object', 'string', 'int'].includes(typeName))
|
||||
if (['object', 'string', 'int', 'long'].includes(typeName))
|
||||
return;
|
||||
if (typeName.endsWith('Option'))
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -427,7 +427,7 @@ class TypesGenerator {
|
|||
return `{ [key: ${keyType}]: ${valueType}; }`;
|
||||
}
|
||||
let out = type.name;
|
||||
if (out === 'int' || out === 'float')
|
||||
if (out === 'int' || out === 'long' || out === 'float')
|
||||
out = 'number';
|
||||
if (out === 'Array' && direction === 'in')
|
||||
out = 'ReadonlyArray';
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ function buildTree(lines) {
|
|||
const headerStack = [root];
|
||||
|
||||
/** @type {{ indent: string, node: MarkdownNode }[]} */
|
||||
let sectionStack = [];
|
||||
const sectionStack = [];
|
||||
|
||||
/**
|
||||
* @param {string} indent
|
||||
|
|
@ -133,7 +133,7 @@ function buildTree(lines) {
|
|||
const appendNode = (indent, node) => {
|
||||
while (sectionStack.length && sectionStack[0].indent.length >= indent.length)
|
||||
sectionStack.shift();
|
||||
const parentNode = sectionStack.length ? sectionStack[0].node :headerStack[0];
|
||||
const parentNode = sectionStack.length ? sectionStack[0].node : headerStack[0];
|
||||
if (!parentNode.children)
|
||||
parentNode.children = [];
|
||||
parentNode.children.push(node);
|
||||
|
|
@ -176,7 +176,7 @@ function buildTree(lines) {
|
|||
line = lines[++i];
|
||||
while (!line.trim().startsWith('```')) {
|
||||
if (line && !line.startsWith(indent)) {
|
||||
const from = Math.max(0, i - 5)
|
||||
const from = Math.max(0, i - 5);
|
||||
const to = Math.min(lines.length, from + 10);
|
||||
const snippet = lines.slice(from, to);
|
||||
throw new Error(`Bad code block: ${snippet.join('\n')}`);
|
||||
|
|
@ -200,7 +200,7 @@ function buildTree(lines) {
|
|||
const tokens = [];
|
||||
while (!line.trim().startsWith(':::')) {
|
||||
if (!line.startsWith(indent)) {
|
||||
const from = Math.max(0, i - 5)
|
||||
const from = Math.max(0, i - 5);
|
||||
const to = Math.min(lines.length, from + 10);
|
||||
const snippet = lines.slice(from, to);
|
||||
throw new Error(`Bad comment block: ${snippet.join('\n')}`);
|
||||
|
|
@ -279,7 +279,7 @@ function parse(content) {
|
|||
function render(nodes, options) {
|
||||
const result = [];
|
||||
let lastNode;
|
||||
for (let node of nodes) {
|
||||
for (const node of nodes) {
|
||||
if (node.type === 'null')
|
||||
continue;
|
||||
innerRenderMdNode('', node, /** @type {MarkdownNode} */ (lastNode), result, options);
|
||||
|
|
@ -322,7 +322,7 @@ function innerRenderMdNode(indent, node, lastNode, result, options) {
|
|||
const bothLinks = node.text.match(/\[[^\]]+\]:/) && lastNode && lastNode.type === 'text' && lastNode.text.match(/\[[^\]]+\]:/);
|
||||
if (!bothTables && !bothGen && !bothComments && !bothLinks && lastNode && lastNode.text)
|
||||
newLine();
|
||||
result.push(wrapText(node.text, options, indent));
|
||||
result.push(wrapText(node.text, options, indent));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -391,15 +391,15 @@ function tokenizeNoBreakLinks(text) {
|
|||
* @param {string} prefix
|
||||
* @returns {string}
|
||||
*/
|
||||
function wrapText(text, options, prefix) {
|
||||
function wrapText(text, options, prefix) {
|
||||
if (options?.flattenText)
|
||||
text = text.replace(/↵/g, ' ');
|
||||
const lines = text.split(/[\n↵]/);
|
||||
const result = /** @type {string[]} */([]);
|
||||
const indent = ' '.repeat(prefix.length);
|
||||
for (const line of lines) {
|
||||
for (const line of lines)
|
||||
result.push(wrapLine(line, options?.maxColumns, result.length ? indent : prefix));
|
||||
}
|
||||
|
||||
return result.join('\n');
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue