Merge branch 'main' into feat-extend-custom-asymmetric-matchers
This commit is contained in:
commit
637a27c9b3
|
|
@ -19,7 +19,7 @@ runs:
|
|||
elif [[ "$version" == "12" || "$version" == "13" ]]; then
|
||||
sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db "INSERT OR REPLACE INTO access VALUES('kTCCServiceMicrophone','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159);"
|
||||
else
|
||||
echo "macOS version is unsupported. Version is $version, exiting"
|
||||
exit 1
|
||||
echo "Skipping unsupported macOS version $version"
|
||||
exit 0
|
||||
fi
|
||||
echo "Successfully allowed microphone access"
|
||||
|
|
|
|||
9
.github/workflows/tests_secondary.yml
vendored
9
.github/workflows/tests_secondary.yml
vendored
|
|
@ -50,10 +50,15 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Intel: macos-13, macos-14-large
|
||||
# Arm64: macos-13-xlarge, macos-14
|
||||
# Intel: macos-13, macos-14-large, macos-15-large
|
||||
# Arm64: macos-13-xlarge, macos-14 macos-15
|
||||
os: [macos-13, macos-13-xlarge, macos-14-large, macos-14]
|
||||
browser: [chromium, firefox, webkit]
|
||||
include:
|
||||
- os: macos-15-large
|
||||
browser: webkit
|
||||
- os: macos-15
|
||||
browser: webkit
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
|
|||
|
|
@ -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://aka.ms/playwright/discord)
|
||||
[](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://aka.ms/playwright/discord)
|
||||
|
||||
## [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 -->130.0.6723.6<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Chromium <!-- GEN:chromium-version -->130.0.6723.19<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->18.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Firefox <!-- GEN:firefox-version -->130.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
|
|
|
|||
|
|
@ -177,7 +177,9 @@ Launches a process in the shell on the device and returns a socket to communicat
|
|||
|
||||
### param: AndroidDevice.open.command
|
||||
* since: v1.9
|
||||
- `command` <[string]> Shell command to execute.
|
||||
- `command` <[string]>
|
||||
|
||||
Shell command to execute.
|
||||
|
||||
## async method: AndroidDevice.pinchClose
|
||||
* since: v1.9
|
||||
|
|
@ -445,7 +447,7 @@ Either a predicate that receives an event or an options object. Optional.
|
|||
* since: v1.9
|
||||
- returns: <[AndroidWebView]>
|
||||
|
||||
This method waits until [AndroidWebView] matching the [`option: selector`] is opened and returns it. If there is already an open [AndroidWebView] matching the [`option: selector`], returns immediately.
|
||||
This method waits until [AndroidWebView] matching the [`param: selector`] is opened and returns it. If there is already an open [AndroidWebView] matching the [`param: selector`], returns immediately.
|
||||
|
||||
### param: AndroidDevice.webView.selector
|
||||
* since: v1.9
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ var data = new Dictionary<string, object>() {
|
|||
await Request.FetchAsync("https://example.com/api/createBook", new() { Method = "post", DataObject = data });
|
||||
```
|
||||
|
||||
The common way to send file(s) in the body of a request is to upload them as form fields with `multipart/form-data` encoding. Use [FormData] to construct request body and pass it to the request as [`option: multipart`] parameter:
|
||||
The common way to send file(s) in the body of a request is to upload them as form fields with `multipart/form-data` encoding, by specifiying the `multipart` parameter:
|
||||
|
||||
```js
|
||||
const form = new FormData();
|
||||
|
|
@ -300,6 +300,7 @@ multipart.Set("fileField", file);
|
|||
await Request.FetchAsync("https://example.com/api/uploadScript", new() { Method = "post", Multipart = multipart });
|
||||
```
|
||||
|
||||
|
||||
### param: APIRequestContext.fetch.urlOrRequest
|
||||
* since: v1.16
|
||||
- `urlOrRequest` <[string]|[Request]>
|
||||
|
|
|
|||
|
|
@ -1198,7 +1198,7 @@ Enabling routing disables http cache.
|
|||
- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]>
|
||||
|
||||
A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
|
||||
When a [`option: baseURL`] via the context options was provided and the passed URL is a path,
|
||||
When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path,
|
||||
it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
|
||||
|
||||
### param: BrowserContext.route.handler
|
||||
|
|
@ -1342,7 +1342,7 @@ await context.RouteWebSocketAsync("/ws", async ws => {
|
|||
* since: v1.48
|
||||
- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]>
|
||||
|
||||
Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the [`option: baseURL`] from the context options.
|
||||
Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the [`option: Browser.newContext.baseURL`] context option.
|
||||
|
||||
### param: BrowserContext.routeWebSocket.handler
|
||||
* since: v1.48
|
||||
|
|
|
|||
|
|
@ -1304,7 +1304,7 @@ Returns whether the element is [enabled](../actionability.md#enabled).
|
|||
* discouraged: Use locator-based [`method: Locator.isHidden`] instead. Read more about [locators](../locators.md).
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is hidden, the opposite of [visible](../actionability.md#visible). [`option: selector`] that does not match any elements is considered hidden.
|
||||
Returns whether the element is hidden, the opposite of [visible](../actionability.md#visible). [`param: selector`] that does not match any elements is considered hidden.
|
||||
|
||||
### param: Frame.isHidden.selector = %%-input-selector-%%
|
||||
* since: v1.8
|
||||
|
|
@ -1322,7 +1322,7 @@ Returns whether the element is hidden, the opposite of [visible](../actionabilit
|
|||
* discouraged: Use locator-based [`method: Locator.isVisible`] instead. Read more about [locators](../locators.md).
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is [visible](../actionability.md#visible). [`option: selector`] that does not match any elements is considered not visible.
|
||||
Returns whether the element is [visible](../actionability.md#visible). [`param: selector`] that does not match any elements is considered not visible.
|
||||
|
||||
### param: Frame.isVisible.selector = %%-input-selector-%%
|
||||
* since: v1.8
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ foreach (var li in await page.GetByRole("listitem").AllAsync())
|
|||
Returns an array of `node.innerText` values for all matching nodes.
|
||||
|
||||
:::warning[Asserting text]
|
||||
If you need to assert text on the page, prefer [`method: LocatorAssertions.toHaveText`] with [`option: useInnerText`] option to avoid flakiness. See [assertions guide](../test-assertions.md) for more details.
|
||||
If you need to assert text on the page, prefer [`method: LocatorAssertions.toHaveText`] with [`option: LocatorAssertions.toHaveText.useInnerText`] option to avoid flakiness. See [assertions guide](../test-assertions.md) for more details.
|
||||
:::
|
||||
|
||||
**Usage**
|
||||
|
|
@ -1291,7 +1291,7 @@ Returns the [`element.innerHTML`](https://developer.mozilla.org/en-US/docs/Web/A
|
|||
Returns the [`element.innerText`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText).
|
||||
|
||||
:::warning[Asserting text]
|
||||
If you need to assert text on the page, prefer [`method: LocatorAssertions.toHaveText`] with [`option: useInnerText`] option to avoid flakiness. See [assertions guide](../test-assertions.md) for more details.
|
||||
If you need to assert text on the page, prefer [`method: LocatorAssertions.toHaveText`] with [`option: LocatorAssertions.toHaveText.useInnerText`] option to avoid flakiness. See [assertions guide](../test-assertions.md) for more details.
|
||||
:::
|
||||
|
||||
### option: Locator.innerText.timeout = %%-input-timeout-%%
|
||||
|
|
|
|||
|
|
@ -2333,10 +2333,57 @@ last redirect. If cannot go forward, returns `null`.
|
|||
|
||||
Navigate to the next page in history.
|
||||
|
||||
## async method: Page.forceGarbageCollection
|
||||
* since: v1.47
|
||||
## async method: Page.requestGC
|
||||
* since: v1.48
|
||||
|
||||
Force the browser to perform garbage collection.
|
||||
Request the page to perform garbage collection. Note that there is no guarantee that all unreachable objects will be collected.
|
||||
|
||||
This is useful to help detect memory leaks. For example, if your page has a large object `'suspect'` that might be leaked, you can check that it does not leak by using a [`WeakRef`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef).
|
||||
|
||||
```js
|
||||
// 1. In your page, save a WeakRef for the "suspect".
|
||||
await page.evaluate(() => globalThis.suspectWeakRef = new WeakRef(suspect));
|
||||
// 2. Request garbage collection.
|
||||
await page.requestGC();
|
||||
// 3. Check that weak ref does not deref to the original object.
|
||||
expect(await page.evaluate(() => !globalThis.suspectWeakRef.deref())).toBe(true);
|
||||
```
|
||||
|
||||
```java
|
||||
// 1. In your page, save a WeakRef for the "suspect".
|
||||
page.evaluate("globalThis.suspectWeakRef = new WeakRef(suspect)");
|
||||
// 2. Request garbage collection.
|
||||
page.requestGC();
|
||||
// 3. Check that weak ref does not deref to the original object.
|
||||
assertTrue(page.evaluate("!globalThis.suspectWeakRef.deref()"));
|
||||
```
|
||||
|
||||
```python async
|
||||
# 1. In your page, save a WeakRef for the "suspect".
|
||||
await page.evaluate("globalThis.suspectWeakRef = new WeakRef(suspect)")
|
||||
# 2. Request garbage collection.
|
||||
await page.request_gc()
|
||||
# 3. Check that weak ref does not deref to the original object.
|
||||
assert await page.evaluate("!globalThis.suspectWeakRef.deref()")
|
||||
```
|
||||
|
||||
```python sync
|
||||
# 1. In your page, save a WeakRef for the "suspect".
|
||||
page.evaluate("globalThis.suspectWeakRef = new WeakRef(suspect)")
|
||||
# 2. Request garbage collection.
|
||||
page.request_gc()
|
||||
# 3. Check that weak ref does not deref to the original object.
|
||||
assert page.evaluate("!globalThis.suspectWeakRef.deref()")
|
||||
```
|
||||
|
||||
```csharp
|
||||
// 1. In your page, save a WeakRef for the "suspect".
|
||||
await Page.EvaluateAsync("globalThis.suspectWeakRef = new WeakRef(suspect)");
|
||||
// 2. Request garbage collection.
|
||||
await Page.RequestGCAsync();
|
||||
// 3. Check that weak ref does not deref to the original object.
|
||||
Assert.True(await Page.EvaluateAsync("!globalThis.suspectWeakRef.deref()"));
|
||||
```
|
||||
|
||||
### option: Page.goForward.waitUntil = %%-navigation-wait-until-%%
|
||||
* since: v1.8
|
||||
|
|
@ -2382,7 +2429,7 @@ Headless mode doesn't support navigation to a PDF document. See the
|
|||
- `url` <[string]>
|
||||
|
||||
URL to navigate page to. The url should include scheme, e.g. `https://`.
|
||||
When a [`option: baseURL`] via the context options was provided and the passed URL is a path,
|
||||
When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path,
|
||||
it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
|
||||
|
||||
### option: Page.goto.waitUntil = %%-navigation-wait-until-%%
|
||||
|
|
@ -2589,7 +2636,7 @@ Returns whether the element is [enabled](../actionability.md#enabled).
|
|||
* discouraged: Use locator-based [`method: Locator.isHidden`] instead. Read more about [locators](../locators.md).
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is hidden, the opposite of [visible](../actionability.md#visible). [`option: selector`] that does not match any elements is considered hidden.
|
||||
Returns whether the element is hidden, the opposite of [visible](../actionability.md#visible). [`param: selector`] that does not match any elements is considered hidden.
|
||||
|
||||
### param: Page.isHidden.selector = %%-input-selector-%%
|
||||
* since: v1.8
|
||||
|
|
@ -2608,7 +2655,7 @@ Returns whether the element is hidden, the opposite of [visible](../actionabilit
|
|||
* discouraged: Use locator-based [`method: Locator.isVisible`] instead. Read more about [locators](../locators.md).
|
||||
- returns: <[boolean]>
|
||||
|
||||
Returns whether the element is [visible](../actionability.md#visible). [`option: selector`] that does not match any elements is considered not visible.
|
||||
Returns whether the element is [visible](../actionability.md#visible). [`param: selector`] that does not match any elements is considered not visible.
|
||||
|
||||
### param: Page.isVisible.selector = %%-input-selector-%%
|
||||
* since: v1.8
|
||||
|
|
@ -2714,8 +2761,7 @@ User can inspect selectors or perform manual steps while paused. Resume will con
|
|||
the place it was paused.
|
||||
|
||||
:::note
|
||||
This method requires Playwright to be started in a headed mode, with a falsy [`option: headless`] value in
|
||||
the [`method: BrowserType.launch`].
|
||||
This method requires Playwright to be started in a headed mode, with a falsy [`option: BrowserType.launch.headless`] option.
|
||||
:::
|
||||
|
||||
## async method: Page.pdf
|
||||
|
|
@ -3092,11 +3138,9 @@ Things to keep in mind:
|
|||
|
||||
:::warning
|
||||
Running the handler will alter your page state mid-test. For example it will change the currently focused element and move the mouse. Make sure that actions that run after the handler are self-contained and do not rely on the focus and mouse state being unchanged.
|
||||
<br />
|
||||
<br />
|
||||
|
||||
For example, consider a test that calls [`method: Locator.focus`] followed by [`method: Keyboard.press`]. If your handler clicks a button between these two actions, the focused element most likely will be wrong, and key press will happen on the unexpected element. Use [`method: Locator.press`] instead to avoid this problem.
|
||||
<br />
|
||||
<br />
|
||||
|
||||
Another example is a series of mouse actions, where [`method: Mouse.move`] is followed by [`method: Mouse.down`]. Again, when the handler runs between these two actions, the mouse position will be wrong during the mouse down. Prefer self-contained actions like [`method: Locator.click`] that do not rely on the state being unchanged by a handler.
|
||||
:::
|
||||
|
||||
|
|
@ -3564,7 +3608,7 @@ Enabling routing disables http cache.
|
|||
- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]>
|
||||
|
||||
A glob pattern, regex pattern or predicate receiving [URL] to match while routing.
|
||||
When a [`option: baseURL`] via the context options was provided and the passed URL is a path,
|
||||
When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path,
|
||||
it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
|
||||
|
||||
### param: Page.route.handler
|
||||
|
|
@ -3708,7 +3752,7 @@ await page.RouteWebSocketAsync("/ws", async ws => {
|
|||
* since: v1.48
|
||||
- `url` <[string]|[RegExp]|[function]\([URL]\):[boolean]>
|
||||
|
||||
Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the [`option: baseURL`] from the context options.
|
||||
Only WebSockets with the url matching this pattern will be routed. A string pattern can be relative to the [`option: Browser.newContext.baseURL`] context option.
|
||||
|
||||
### param: Page.routeWebSocket.handler
|
||||
* since: v1.48
|
||||
|
|
@ -4049,12 +4093,16 @@ await page.GotoAsync("https://www.microsoft.com");
|
|||
### param: Page.setViewportSize.width
|
||||
* since: v1.10
|
||||
* langs: csharp, java
|
||||
- `width` <[int]> page width in pixels.
|
||||
- `width` <[int]>
|
||||
|
||||
Page width in pixels.
|
||||
|
||||
### param: Page.setViewportSize.height
|
||||
* since: v1.10
|
||||
* langs: csharp, java
|
||||
- `height` <[int]> page height in pixels.
|
||||
- `height` <[int]>
|
||||
|
||||
Page height in pixels.
|
||||
|
||||
## async method: Page.tap
|
||||
* since: v1.8
|
||||
|
|
@ -4072,7 +4120,7 @@ When all steps combined have not finished during the specified [`option: timeout
|
|||
[TimeoutError]. Passing zero timeout disables this.
|
||||
|
||||
:::note
|
||||
[`method: Page.tap`] the method will throw if [`option: hasTouch`] option of the browser context is false.
|
||||
[`method: Page.tap`] the method will throw if [`option: Browser.newContext.hasTouch`] option of the browser context is false.
|
||||
:::
|
||||
|
||||
### param: Page.tap.selector = %%-input-selector-%%
|
||||
|
|
@ -4859,7 +4907,7 @@ await page.RunAndWaitForRequestAsync(async () =>
|
|||
- `urlOrPredicate` <[string]|[RegExp]|[function]\([Request]\):[boolean]>
|
||||
|
||||
Request URL string, regex or predicate receiving [Request] object.
|
||||
When a [`option: baseURL`] via the context options was provided and the passed URL is a path,
|
||||
When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path,
|
||||
it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
|
||||
|
||||
### param: Page.waitForRequest.urlOrPredicate
|
||||
|
|
@ -5003,7 +5051,7 @@ await page.RunAndWaitForResponseAsync(async () =>
|
|||
- `urlOrPredicate` <[string]|[RegExp]|[function]\([Response]\):[boolean]>
|
||||
|
||||
Request URL string, regex or predicate receiving [Response] object.
|
||||
When a [`option: baseURL`] via the context options was provided and the passed URL is a path,
|
||||
When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path,
|
||||
it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
|
||||
|
||||
### param: Page.waitForResponse.urlOrPredicate
|
||||
|
|
@ -5012,7 +5060,7 @@ it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/We
|
|||
- `urlOrPredicate` <[string]|[RegExp]|[function]\([Response]\):[boolean]|[Promise]<[boolean]>>
|
||||
|
||||
Request URL string, regex or predicate receiving [Response] object.
|
||||
When a [`option: baseURL`] via the context options was provided and the passed URL is a path,
|
||||
When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path,
|
||||
it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
|
||||
|
||||
### option: Page.waitForResponse.timeout
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ touchscreen can only be used in browser contexts that have been initialized with
|
|||
Dispatches a `touchstart` and `touchend` event with a single touch at the position ([`param: x`],[`param: y`]).
|
||||
|
||||
:::note
|
||||
[`method: Page.tap`] the method will throw if [`option: hasTouch`] option of the browser context is false.
|
||||
[`method: Page.tap`] the method will throw if [`option: Browser.newContext.hasTouch`] option of the browser context is false.
|
||||
:::
|
||||
|
||||
### param: Touchscreen.tap.x
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ await context.Tracing.StopAsync(new()
|
|||
- `name` <[string]>
|
||||
|
||||
If specified, intermediate trace files are going to be saved into the files with the
|
||||
given name prefix inside the [`option: tracesDir`] folder specified in [`method: BrowserType.launch`].
|
||||
given name prefix inside the [`option: BrowserType.launch.tracesDir`] directory specified in [`method: BrowserType.launch`].
|
||||
To specify the final trace zip file name, you need to pass `path` option to
|
||||
[`method: Tracing.stop`] instead.
|
||||
|
||||
|
|
@ -277,7 +277,7 @@ Trace name to be shown in the Trace Viewer.
|
|||
- `name` <[string]>
|
||||
|
||||
If specified, intermediate trace files are going to be saved into the files with the
|
||||
given name prefix inside the [`option: tracesDir`] folder specified in [`method: BrowserType.launch`].
|
||||
given name prefix inside the [`option: BrowserType.launch.tracesDir`] directory specified in [`method: BrowserType.launch`].
|
||||
To specify the final trace zip file name, you need to pass `path` option to
|
||||
[`method: Tracing.stopChunk`] instead.
|
||||
|
||||
|
|
|
|||
|
|
@ -699,7 +699,7 @@ Logger sink for Playwright logging.
|
|||
- `content` ?<[HarContentPolicy]<"omit"|"embed"|"attach">> Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` is specified, resources are persisted as separate files or entries in the ZIP archive. If `embed` is specified, content is stored inline the HAR file as per HAR specification. Defaults to `attach` for `.zip` output files and to `embed` for all other file extensions.
|
||||
- `path` <[path]> Path on the filesystem to write the HAR file to. If the file name ends with `.zip`, `content: 'attach'` is used by default.
|
||||
- `mode` ?<[HarMode]<"full"|"minimal">> When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`.
|
||||
- `urlFilter` ?<[string]|[RegExp]> A glob or regex pattern to filter requests that are stored in the HAR. When a [`option: baseURL`] via the context options was provided and the passed URL is a path, it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. Defaults to none.
|
||||
- `urlFilter` ?<[string]|[RegExp]> A glob or regex pattern to filter requests that are stored in the HAR. When a [`option: Browser.newContext.baseURL`] via the context options was provided and the passed URL is a path, it gets merged via the [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. Defaults to none.
|
||||
|
||||
Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. If not
|
||||
specified, the HAR is not recorded. Make sure to await [`method: BrowserContext.close`] for the HAR to be
|
||||
|
|
@ -765,8 +765,6 @@ not recorded. Make sure to call [`method: BrowserContext.close`] for videos to b
|
|||
* langs: csharp, java, python
|
||||
- alias-python: record_video_size
|
||||
- `recordVideoSize` <[Object]>
|
||||
If `viewport` is not configured explicitly the video size defaults to 800x450. Actual picture of each page will be
|
||||
scaled down if necessary to fit the specified size.
|
||||
- `width` <[int]> Video frame width.
|
||||
- `height` <[int]> Video frame height.
|
||||
|
||||
|
|
@ -1046,7 +1044,7 @@ Close the browser process on SIGHUP. Defaults to `true`.
|
|||
Whether to run browser in headless mode. More details for
|
||||
[Chromium](https://developers.google.com/web/updates/2017/04/headless-chrome) and
|
||||
[Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode). Defaults to `true` unless the
|
||||
[`option: devtools`] option is `true`.
|
||||
[`option: BrowserType.launch.devtools`] option is `true`.
|
||||
|
||||
## js-python-browser-option-firefoxuserprefs
|
||||
* langs: js, python
|
||||
|
|
|
|||
|
|
@ -451,7 +451,7 @@ will reset pre-configured user agent and device emulation.
|
|||
Playwright runs browsers in headless mode by default. To change this behavior,
|
||||
use `headless: false` as a launch option.
|
||||
|
||||
You can also use the [`option: slowMo`] option
|
||||
You can also use the [`option: BrowserType.launch.slowMo`] option
|
||||
to slow down execution (by N milliseconds per operation) and follow along while debugging.
|
||||
|
||||
```js
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ If there is no listener for [`event: Page.dialog`], all dialogs are automaticall
|
|||
|
||||
## beforeunload dialog
|
||||
|
||||
When [`method: Page.close`] is invoked with the truthy [`option: runBeforeUnload`] value, the page runs its unload handlers. This is the only case when [`method: Page.close`] does not wait for the page to actually close, because it might be that the page stays open in the end of the operation.
|
||||
When [`method: Page.close`] is invoked with the truthy [`option: Page.close.runBeforeUnload`] value, the page runs its unload handlers. This is the only case when [`method: Page.close`] does not wait for the page to actually close, because it might be that the page stays open in the end of the operation.
|
||||
|
||||
You can register a dialog handler to handle the `beforeunload` dialog yourself:
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ title: "Downloads"
|
|||
|
||||
For every attachment downloaded by the page, [`event: Page.download`] event is emitted. All these attachments are downloaded into a temporary folder. You can obtain the download url, file name and payload stream using the [Download] object from the event.
|
||||
|
||||
You can specify where to persist downloaded files using the [`option: downloadsPath`] option in [`method: BrowserType.launch`].
|
||||
You can specify where to persist downloaded files using the [`option: BrowserType.launch.downloadsPath`] option in [`method: BrowserType.launch`].
|
||||
|
||||
:::note
|
||||
Downloaded files are deleted when the browser context that produced them is closed.
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ public class App {
|
|||
}
|
||||
```
|
||||
|
||||
By default, Playwright runs the browsers in headless mode. To see the browser UI, pass the `setHeadless(false)` flag while launching the browser. You can also use [`option: slowMo`] to slow down execution. Learn more in the debugging tools [section](./debug.md).
|
||||
By default, Playwright runs the browsers in headless mode. To see the browser UI, [`option: BrowserType.launch.headless`] option to `false`. You can also use [`option: BrowserType.launch.slowMo`] to slow down execution. Learn more in the debugging tools [section](./debug.md).
|
||||
|
||||
```java
|
||||
playwright.firefox().launch(new BrowserType.LaunchOptions().setHeadless(false).setSlowMo(50));
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ Now run it.
|
|||
dotnet run
|
||||
```
|
||||
|
||||
By default, Playwright runs the browsers in headless mode. To see the browser UI, pass the `Headless = false` flag while launching the browser. You can also use [`option: slowMo`] to slow down execution. Learn more in the debugging tools [section](./debug.md).
|
||||
By default, Playwright runs the browsers in headless mode. To see the browser UI, set [`option: BrowserType.launch.headless`] option to `false`. You can also use [`option: BrowserType.launch.slowMo`] to slow down execution. Learn more in the debugging tools [section](./debug.md).
|
||||
|
||||
```csharp
|
||||
await using var browser = await playwright.Firefox.LaunchAsync(new()
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ with sync_playwright() as p:
|
|||
browser.close()
|
||||
```
|
||||
|
||||
By default, Playwright runs the browsers in headless mode. To see the browser UI, pass the `headless=False` flag while launching the browser. You can also use [`option: slowMo`] to slow down execution. Learn more in the debugging tools [section](./debug.md).
|
||||
By default, Playwright runs the browsers in headless mode. To see the browser UI, set [`option: BrowserType.launch.headless`] option to `False`. You can also use [`option: BrowserType.launch.slowMo`] to slow down execution. Learn more in the debugging tools [section](./debug.md).
|
||||
|
||||
```py
|
||||
firefox.launch(headless=False, slow_mo=50)
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ The Network tab in the trace viewer has several nice improvements:
|
|||
- The `mcr.microsoft.com/playwright/dotnet:v1.47.0` now serves a Playwright image based on Ubuntu 24.04 Noble.
|
||||
To use the 22.04 jammy-based image, please use `mcr.microsoft.com/playwright/dotnet:v1.47.0-jammy` instead.
|
||||
- The `:latest`/`:focal`/`:jammy` tag for Playwright Docker images is no longer being published. Pin to a specific version for better stability and reproducibility.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: cert`] and [`option: key`] as byte arrays instead of file paths.
|
||||
- [`option: noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: Browser.newContext.clientCertificates.cert`] and [`option: Browser.newContext.clientCertificates.key`] as byte arrays instead of file paths.
|
||||
- [`option: Locator.selectOption.noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- We've seen reports of WebGL in Webkit misbehaving on GitHub Actions `macos-13`. We recommend upgrading GitHub Actions to `macos-14`.
|
||||
|
||||
### Browser Versions
|
||||
|
|
@ -284,7 +284,7 @@ await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Light and easy" })
|
|||
|
||||
### New APIs
|
||||
|
||||
- [`method: Page.pdf`] accepts two new options [`option: tagged`] and [`option: outline`].
|
||||
- [`method: Page.pdf`] accepts two new options [`option: Page.pdf.tagged`] and [`option: Page.pdf.outline`].
|
||||
|
||||
### Announcements
|
||||
|
||||
|
|
@ -307,7 +307,7 @@ This version was also tested against the following stable channels:
|
|||
|
||||
- New method [`method: Page.unrouteAll`] removes all routes registered by [`method: Page.route`] and [`method: Page.routeFromHAR`]. Optionally allows to wait for ongoing routes to finish, or ignore any errors from them.
|
||||
- New method [`method: BrowserContext.unrouteAll`] removes all routes registered by [`method: BrowserContext.route`] and [`method: BrowserContext.routeFromHAR`]. Optionally allows to wait for ongoing routes to finish, or ignore any errors from them.
|
||||
- New option [`option: style`] in [`method: Page.screenshot`] and [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
- New options [`option: Page.screenshot.style`] in [`method: Page.screenshot`] and [`option: Locator.screenshot.style`] in [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
|
|
@ -345,8 +345,8 @@ await Expect(Page.GetByPlaceholder("Search docs")).ToHaveValueAsync("locator");
|
|||
|
||||
### New APIs
|
||||
|
||||
- Option [`option: reason`] in [`method: Page.close`], [`method: BrowserContext.close`] and [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
- Options [`option: Page.close.reason`] in [`method: Page.close`], [`option: BrowserContext.close.reason`] in [`method: BrowserContext.close`] and [`option: Browser.close.reason`] in [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: BrowserType.launchPersistentContext.firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
|
||||
### Other Changes
|
||||
|
||||
|
|
@ -517,7 +517,7 @@ This version was also tested against the following stable channels:
|
|||
await Page.GetByRole(AriaRole.Button, new() { Name = "Dismiss" }).ClickAsync();
|
||||
await newEmail.ClickAsync();
|
||||
```
|
||||
* Use new options [`option: hasNot`] and [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
* Use new options [`option: Locator.filter.hasNot`] and [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
to find elements that **do not match** certain conditions.
|
||||
|
||||
```csharp
|
||||
|
|
@ -534,10 +534,10 @@ This version was also tested against the following stable channels:
|
|||
### New APIs
|
||||
|
||||
- [`method: Locator.or`]
|
||||
- New option [`option: hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
- New option [`option: Locator.filter.hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
- [`method: LocatorAssertions.toBeAttached`]
|
||||
- New option [`option: timeout`] in [`method: Route.fetch`]
|
||||
- New option [`option: Route.fetch.timeout`] in [`method: Route.fetch`]
|
||||
|
||||
### ⚠️ Breaking change
|
||||
|
||||
|
|
@ -560,9 +560,9 @@ This version was also tested against the following stable channels:
|
|||
|
||||
### New APIs
|
||||
|
||||
- New options [`option: updateMode`] and [`option: updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- New options [`option: Page.routeFromHAR.updateMode`] and [`option: Page.routeFromHAR.updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- Chaining existing locator objects, see [locator docs](./locators.md#matching-inside-a-locator) for details.
|
||||
- New option [`option: name`] in method [`method: Tracing.startChunk`].
|
||||
- New option [`option: Tracing.startChunk.name`] in method [`method: Tracing.startChunk`].
|
||||
|
||||
### Browser Versions
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ The Network tab in the trace viewer has several nice improvements:
|
|||
- The `mcr.microsoft.com/playwright/java:v1.47.0` now serves a Playwright image based on Ubuntu 24.04 Noble.
|
||||
To use the 22.02 jammy-based image, please use `mcr.microsoft.com/playwright/java:v1.47.0-jammy` instead.
|
||||
- The `:latest`/`:focal`/`:jammy` tag for Playwright Docker images is no longer being published. Pin to a specific version for better stability and reproducibility.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: cert`] and [`option: key`] as byte arrays instead of file paths.
|
||||
- [`option: noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: Browser.newContext.clientCertificates.cert`] and [`option: Browser.newContext.clientCertificates.key`] as byte arrays instead of file paths.
|
||||
- [`option: Locator.selectOption.noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- We've seen reports of WebGL in Webkit misbehaving on GitHub Actions `macos-13`. We recommend upgrading GitHub Actions to `macos-14`.
|
||||
|
||||
### Browser Versions
|
||||
|
|
@ -349,7 +349,7 @@ assertThat(page.getByRole(AriaRole.HEADING, new Page.GetByRoleOptions().setName(
|
|||
|
||||
### New APIs
|
||||
|
||||
- [`method: Page.pdf`] accepts two new options [`option: tagged`] and [`option: outline`].
|
||||
- [`method: Page.pdf`] accepts two new options [`option: Page.pdf.tagged`] and [`option: Page.pdf.outline`].
|
||||
|
||||
### Announcements
|
||||
|
||||
|
|
@ -372,7 +372,7 @@ This version was also tested against the following stable channels:
|
|||
|
||||
- New method [`method: Page.unrouteAll`] removes all routes registered by [`method: Page.route`] and [`method: Page.routeFromHAR`].
|
||||
- New method [`method: BrowserContext.unrouteAll`] removes all routes registered by [`method: BrowserContext.route`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- New option [`option: style`] in [`method: Page.screenshot`] and [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
- New options [`option: Page.screenshot.style`] in [`method: Page.screenshot`] and [`option: Locator.screenshot.style`] in [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
|
|
@ -410,8 +410,8 @@ assertThat(page.getByPlaceholder("Search docs")).hasValue("locator");
|
|||
|
||||
### New APIs
|
||||
|
||||
- Option [`option: reason`] in [`method: Page.close`], [`method: BrowserContext.close`] and [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
- Options [`option: Page.close.reason`] in [`method: Page.close`], [`option: BrowserContext.close.reason`] in [`method: BrowserContext.close`] and [`option: Browser.close.reason`] in [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: BrowserType.launchPersistentContext.firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
|
||||
### Other Changes
|
||||
|
||||
|
|
@ -597,7 +597,7 @@ This version was also tested against the following stable channels:
|
|||
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Dismiss")).click();
|
||||
newEmail.click();
|
||||
```
|
||||
* Use new options [`option: hasNot`] and [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
* Use new options [`option: Locator.filter.hasNot`] and [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
to find elements that **do not match** certain conditions.
|
||||
|
||||
```java
|
||||
|
|
@ -616,10 +616,10 @@ This version was also tested against the following stable channels:
|
|||
### New APIs
|
||||
|
||||
- [`method: Locator.or`]
|
||||
- New option [`option: hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
- New option [`option: Locator.filter.hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
- [`method: LocatorAssertions.toBeAttached`]
|
||||
- New option [`option: timeout`] in [`method: Route.fetch`]
|
||||
- New option [`option: Route.fetch.timeout`] in [`method: Route.fetch`]
|
||||
|
||||
### Other highlights
|
||||
|
||||
|
|
@ -646,9 +646,9 @@ This version was also tested against the following stable channels:
|
|||
|
||||
### New APIs
|
||||
|
||||
- New options [`option: updateMode`] and [`option: updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- New options [`option: Page.routeFromHAR.updateMode`] and [`option: Page.routeFromHAR.updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- Chaining existing locator objects, see [locator docs](./locators.md#matching-inside-a-locator) for details.
|
||||
- New option [`option: name`] in method [`method: Tracing.startChunk`].
|
||||
- New option [`option: Tracing.startChunk.name`] in method [`method: Tracing.startChunk`].
|
||||
|
||||
### Browser Versions
|
||||
|
||||
|
|
|
|||
|
|
@ -44,16 +44,16 @@ test('query params', async ({ request }) => {
|
|||
);
|
||||
// ...
|
||||
});
|
||||
```
|
||||
```
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- The `mcr.microsoft.com/playwright:v1.47.0` now serves a Playwright image based on Ubuntu 24.04 Noble.
|
||||
To use the 22.04 jammy-based image, please use `mcr.microsoft.com/playwright:v1.47.0-jammy` instead.
|
||||
- New option [`option: behavior`] in [`method: Page.removeAllListeners`], [`method: Browser.removeAllListeners`] and [`method: BrowserContext.removeAllListeners`] to wait for ongoing listeners to complete.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: cert`] and [`option: key`] as buffers instead of file paths.
|
||||
- New options [`option: Page.removeAllListeners.behavior`], [`option: Browser.removeAllListeners.behavior`] and [`option: BrowserContext.removeAllListeners.behavior`] to wait for ongoing listeners to complete.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: Browser.newContext.clientCertificates.cert`] and [`option: Browser.newContext.clientCertificates.key`] as buffers instead of file paths.
|
||||
- Attachments with a `text/html` content type can now be opened in a new tab in the HTML report. This is useful for including third-party reports or other HTML content in the Playwright test report and distributing it to your team.
|
||||
- [`option: noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- [`option: Locator.selectOption.noWaitAfter`] option in [`method: Locator.selectOption`] was deprecated.
|
||||
- We've seen reports of WebGL in Webkit misbehaving on GitHub Actions `macos-13`. We recommend upgrading GitHub Actions to `macos-14`.
|
||||
|
||||
### Browser Versions
|
||||
|
|
@ -528,7 +528,7 @@ This version was also tested against the following stable channels:
|
|||
|
||||
- New method [`method: Page.unrouteAll`] removes all routes registered by [`method: Page.route`] and [`method: Page.routeFromHAR`]. Optionally allows to wait for ongoing routes to finish, or ignore any errors from them.
|
||||
- New method [`method: BrowserContext.unrouteAll`] removes all routes registered by [`method: BrowserContext.route`] and [`method: BrowserContext.routeFromHAR`]. Optionally allows to wait for ongoing routes to finish, or ignore any errors from them.
|
||||
- New option [`option: style`] in [`method: Page.screenshot`] and [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
- New options [`option: Page.screenshot.style`] in [`method: Page.screenshot`] and [`option: Locator.screenshot.style`] in [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
- New option `stylePath` for methods [`method: PageAssertions.toHaveScreenshot#1`] and [`method: LocatorAssertions.toHaveScreenshot#1`] to apply a custom stylesheet while making the screenshot.
|
||||
- New `fileName` option for [Blob reporter](./test-reporters#blob-reporter), to specify the name of the report to be created.
|
||||
|
||||
|
|
@ -577,8 +577,8 @@ test('test', async ({ page }) => {
|
|||
|
||||
### New APIs
|
||||
|
||||
- Option [`option: reason`] in [`method: Page.close`], [`method: BrowserContext.close`] and [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
- Options [`option: Page.close.reason`] in [`method: Page.close`], [`option: BrowserContext.close.reason`] in [`method: BrowserContext.close`] and [`option: Browser.close.reason`] in [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: BrowserType.launchPersistentContext.firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
|
||||
### Other Changes
|
||||
|
||||
|
|
@ -1047,7 +1047,7 @@ This version was also tested against the following stable channels:
|
|||
await page.getByRole('button', { name: 'Dismiss' }).click();
|
||||
await newEmail.click();
|
||||
```
|
||||
* Use new options [`option: hasNot`] and [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
* Use new options [`option: Locator.filter.hasNot`] and [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
to find elements that **do not match** certain conditions.
|
||||
|
||||
```js
|
||||
|
|
@ -1064,10 +1064,10 @@ This version was also tested against the following stable channels:
|
|||
### New APIs
|
||||
|
||||
- [`method: Locator.or`]
|
||||
- New option [`option: hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
- New option [`option: Locator.filter.hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
- [`method: LocatorAssertions.toBeAttached`]
|
||||
- New option [`option: timeout`] in [`method: Route.fetch`]
|
||||
- New option [`option: Route.fetch.timeout`] in [`method: Route.fetch`]
|
||||
- [`method: Reporter.onExit`]
|
||||
|
||||
### ⚠️ Breaking change
|
||||
|
|
@ -1107,10 +1107,10 @@ npx playwright test --ui
|
|||
|
||||
### New APIs
|
||||
|
||||
- New options [`option: updateMode`] and [`option: updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- New options [`option: Page.routeFromHAR.updateMode`] and [`option: Page.routeFromHAR.updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- Chaining existing locator objects, see [locator docs](./locators.md#matching-inside-a-locator) for details.
|
||||
- New property [`property: TestInfo.testId`].
|
||||
- New option [`option: name`] in method [`method: Tracing.startChunk`].
|
||||
- New option [`option: Tracing.startChunk.name`] in method [`method: Tracing.startChunk`].
|
||||
|
||||
|
||||
### ⚠️ Breaking change in component tests
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ The Network tab in the trace viewer has several nice improvements:
|
|||
- The `mcr.microsoft.com/playwright/python:v1.47.0` now serves a Playwright image based on Ubuntu 24.04 Noble.
|
||||
To use the 22.04 jammy-based image, please use `mcr.microsoft.com/playwright/python:v1.47.0-jammy` instead.
|
||||
- The `:latest`/`:focal`/`:jammy` tag for Playwright Docker images is no longer being published. Pin to a specific version for better stability and reproducibility.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: cert`] and [`option: key`] as bytes instead of file paths.
|
||||
- [`option: noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- TLS client certificates can now be passed from memory by passing [`option: Browser.newContext.clientCertificates.cert`] and [`option: Browser.newContext.clientCertificates.key`] as bytes instead of file paths.
|
||||
- [`option: Locator.selectOption.noWaitAfter`] in [`method: Locator.selectOption`] was deprecated.
|
||||
- We've seen reports of WebGL in Webkit misbehaving on GitHub Actions `macos-13`. We recommend upgrading GitHub Actions to `macos-14`.
|
||||
|
||||
### Browser Versions
|
||||
|
|
@ -260,7 +260,7 @@ expect(page.get_by_role("heading", name="Light and easy")).to_be_visible()
|
|||
|
||||
### New APIs
|
||||
|
||||
- [`method: Page.pdf`] accepts two new options [`option: tagged`] and [`option: outline`].
|
||||
- [`method: Page.pdf`] accepts two new options [`option: Page.pdf.tagged`] and [`option: Page.pdf.outline`].
|
||||
|
||||
### Announcements
|
||||
|
||||
|
|
@ -283,7 +283,7 @@ This version was also tested against the following stable channels:
|
|||
|
||||
- New method [`method: Page.unrouteAll`] removes all routes registered by [`method: Page.route`] and [`method: Page.routeFromHAR`]. Optionally allows to wait for ongoing routes to finish, or ignore any errors from them.
|
||||
- New method [`method: BrowserContext.unrouteAll`] removes all routes registered by [`method: BrowserContext.route`] and [`method: BrowserContext.routeFromHAR`]. Optionally allows to wait for ongoing routes to finish, or ignore any errors from them.
|
||||
- New option [`option: style`] in [`method: Page.screenshot`] and [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
- New options [`option: Page.screenshot.style`] in [`method: Page.screenshot`] and [`option: Locator.screenshot.style`] in [`method: Locator.screenshot`] to add custom CSS to the page before taking a screenshot.
|
||||
|
||||
### Browser Versions
|
||||
|
||||
|
|
@ -324,8 +324,8 @@ def test_example(page: Page) -> None:
|
|||
|
||||
### New APIs
|
||||
|
||||
- Option [`option: reason`] in [`method: Page.close`], [`method: BrowserContext.close`] and [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
- Options [`option: Page.close.reason`] in [`method: Page.close`], [`option: BrowserContext.close.reason`] in [`method: BrowserContext.close`] and [`option: Browser.close.reason`] in [`method: Browser.close`]. Close reason is reported for all operations interrupted by the closure.
|
||||
- Option [`option: BrowserType.launchPersistentContext.firefoxUserPrefs`] in [`method: BrowserType.launchPersistentContext`].
|
||||
|
||||
### Other Changes
|
||||
|
||||
|
|
@ -504,7 +504,7 @@ This version was also tested against the following stable channels:
|
|||
page.get_by_role("button", name="Dismiss").click()
|
||||
new_email.click()
|
||||
```
|
||||
* Use new options [`option: hasNot`] and [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
* Use new options [`option: Locator.filter.hasNot`] and [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
to find elements that **do not match** certain conditions.
|
||||
|
||||
```python
|
||||
|
|
@ -520,10 +520,10 @@ This version was also tested against the following stable channels:
|
|||
### New APIs
|
||||
|
||||
- [`method: Locator.or`]
|
||||
- New option [`option: hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: hasNotText`] in [`method: Locator.filter`]
|
||||
- New option [`option: Locator.filter.hasNot`] in [`method: Locator.filter`]
|
||||
- New option [`option: Locator.filter.hasNotText`] in [`method: Locator.filter`]
|
||||
- [`method: LocatorAssertions.toBeAttached`]
|
||||
- New option [`option: timeout`] in [`method: Route.fetch`]
|
||||
- New option [`option: Route.fetch.timeout`] in [`method: Route.fetch`]
|
||||
|
||||
### ⚠️ Breaking change
|
||||
|
||||
|
|
@ -547,9 +547,9 @@ This version was also tested against the following stable channels:
|
|||
### New APIs
|
||||
|
||||
- Custom expect message, see [test assertions documentation](./test-assertions.md#custom-expect-message).
|
||||
- New options [`option: updateMode`] and [`option: updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- New options [`option: Page.routeFromHAR.updateMode`] and [`option: Page.routeFromHAR.updateContent`] in [`method: Page.routeFromHAR`] and [`method: BrowserContext.routeFromHAR`].
|
||||
- Chaining existing locator objects, see [locator docs](./locators.md#matching-inside-a-locator) for details.
|
||||
- New option [`option: name`] in method [`method: Tracing.startChunk`].
|
||||
- New option [`option: Tracing.startChunk.name`] in method [`method: Tracing.startChunk`].
|
||||
|
||||
### Browser Versions
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ Playwright can connect to [Selenium Grid Hub](https://www.selenium.dev/documenta
|
|||
:::warning
|
||||
There is a risk of Playwright integration with Selenium Grid Hub breaking in the future. Make sure you weight risks against benefits before using it.
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<details>
|
||||
<summary>
|
||||
<span style={{textTransform:'uppercase',fontSize:'smaller',fontWeight:'bold',opacity:'0.6'}}>More details</span>
|
||||
|
|
|
|||
|
|
@ -1713,7 +1713,8 @@ Whether to box the step in the report. Defaults to `false`. When the step is box
|
|||
### option: Test.step.location
|
||||
* since: v1.48
|
||||
- `location` <[Location]>
|
||||
Specifies a custom location for the step to be shown in test reports. By default, location of the [`method: Test.step`] call is shown.
|
||||
|
||||
Specifies a custom location for the step to be shown in test reports and trace viewer. By default, location of the [`method: Test.step`] call is shown.
|
||||
|
||||
## method: Test.use
|
||||
* since: v1.10
|
||||
|
|
|
|||
|
|
@ -41,12 +41,12 @@ export default defineConfig({
|
|||
- type: ?<[Object]>
|
||||
- `timeout` ?<[int]> Default timeout for async expect matchers in milliseconds, defaults to 5000ms.
|
||||
- `toHaveScreenshot` ?<[Object]> Configuration for the [`method: PageAssertions.toHaveScreenshot#1`] method.
|
||||
- `animations` ?<[ScreenshotAnimations]<"allow"|"disabled">> See [`option: animations`] in [`method: Page.screenshot`]. Defaults to `"disabled"`.
|
||||
- `caret` ?<[ScreenshotCaret]<"hide"|"initial">> See [`option: caret`] in [`method: Page.screenshot`]. Defaults to `"hide"`.
|
||||
- `animations` ?<[ScreenshotAnimations]<"allow"|"disabled">> See [`option: Page.screenshot.animations`] in [`method: Page.screenshot`]. Defaults to `"disabled"`.
|
||||
- `caret` ?<[ScreenshotCaret]<"hide"|"initial">> See [`option: Page.screenshot.caret`] in [`method: Page.screenshot`]. Defaults to `"hide"`.
|
||||
- `maxDiffPixels` ?<[int]> An acceptable amount of pixels that could be different, unset by default.
|
||||
- `maxDiffPixelRatio` ?<[float]> An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default.
|
||||
- `scale` ?<[ScreenshotScale]<"css"|"device">> See [`option: scale`] in [`method: Page.screenshot`]. Defaults to `"css"`.
|
||||
- `stylePath` ?<[string]|[Array]<[string]>> See [`option: style`] in [`method: Page.screenshot`].
|
||||
- `scale` ?<[ScreenshotScale]<"css"|"device">> See [`option: Page.screenshot.scale`] in [`method: Page.screenshot`]. Defaults to `"css"`.
|
||||
- `stylePath` ?<[string]|[Array]<[string]>> See [`option: Page.screenshot.style`] in [`method: Page.screenshot`].
|
||||
- `threshold` ?<[float]> An acceptable perceived color difference between the same pixel in compared images, ranging from `0` (strict) and `1` (lax). `"pixelmatch"` comparator computes color difference in [YIQ color space](https://en.wikipedia.org/wiki/YIQ) and defaults `threshold` value to `0.2`.
|
||||
- `toMatchSnapshot` ?<[Object]> Configuration for the [`method: SnapshotAssertions.toMatchSnapshot#1`] method.
|
||||
- `maxDiffPixels` ?<[int]> An acceptable amount of pixels that could be different, unset by default.
|
||||
|
|
|
|||
|
|
@ -216,7 +216,9 @@ Test function as passed to `test(title, testFunction)`.
|
|||
|
||||
Tags that apply to the test. Learn more about [tags](../test-annotations.md#tag-tests).
|
||||
|
||||
Note that any changes made to this list while the test is running will not be visible to test reporters.
|
||||
:::note
|
||||
Any changes made to this list while the test is running will not be visible to test reporters.
|
||||
:::
|
||||
|
||||
## property: TestInfo.testId
|
||||
* since: v1.32
|
||||
|
|
|
|||
|
|
@ -94,10 +94,10 @@ export default defineConfig({
|
|||
- `threshold` ?<[float]> an acceptable perceived color difference between the same pixel in compared images, ranging from `0` (strict) and `1` (lax). `"pixelmatch"` comparator computes color difference in [YIQ color space](https://en.wikipedia.org/wiki/YIQ) and defaults `threshold` value to `0.2`.
|
||||
- `maxDiffPixels` ?<[int]> an acceptable amount of pixels that could be different, unset by default.
|
||||
- `maxDiffPixelRatio` ?<[float]> an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default.
|
||||
- `animations` ?<[ScreenshotAnimations]<"allow"|"disabled">> See [`option: animations`] in [`method: Page.screenshot`]. Defaults to `"disabled"`.
|
||||
- `caret` ?<[ScreenshotCaret]<"hide"|"initial">> See [`option: caret`] in [`method: Page.screenshot`]. Defaults to `"hide"`.
|
||||
- `scale` ?<[ScreenshotScale]<"css"|"device">> See [`option: scale`] in [`method: Page.screenshot`]. Defaults to `"css"`.
|
||||
- `stylePath` ?<[string]|[Array]<[string]>> See [`option: style`] in [`method: Page.screenshot`].
|
||||
- `animations` ?<[ScreenshotAnimations]<"allow"|"disabled">> See [`option: Page.screenshot.animations`] in [`method: Page.screenshot`]. Defaults to `"disabled"`.
|
||||
- `caret` ?<[ScreenshotCaret]<"hide"|"initial">> See [`option: Page.screenshot.caret`] in [`method: Page.screenshot`]. Defaults to `"hide"`.
|
||||
- `scale` ?<[ScreenshotScale]<"css"|"device">> See [`option: Page.screenshot.scale`] in [`method: Page.screenshot`]. Defaults to `"css"`.
|
||||
- `stylePath` ?<[string]|[Array]<[string]>> See [`option: Page.screenshot.style`] in [`method: Page.screenshot`].
|
||||
- `toMatchSnapshot` ?<[Object]> Configuration for the [`method: SnapshotAssertions.toMatchSnapshot#1`] method.
|
||||
- `threshold` ?<[float]> an acceptable perceived color difference between the same pixel in compared images, ranging from `0` (strict) and `1` (lax). `"pixelmatch"` comparator computes color difference in [YIQ color space](https://en.wikipedia.org/wiki/YIQ) and defaults `threshold` value to `0.2`.
|
||||
- `maxDiffPixels` ?<[int]> an acceptable amount of pixels that could be different, unset by default.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ In the Actions tab you can see what locator was used for every action and how lo
|
|||
|
||||
### Screenshots
|
||||
|
||||
When tracing with the [`option: screenshots`] option turned on (default), each trace records a screencast and renders it as a film strip. You can hover over the film strip to see a magnified image of for each action and state which helps you easily find the action you want to inspect.
|
||||
When tracing with the [`option: Tracing.start.screenshots`] option turned on (default), each trace records a screencast and renders it as a film strip. You can hover over the film strip to see a magnified image of for each action and state which helps you easily find the action you want to inspect.
|
||||
|
||||
Double click on an action to see the time range for that action. You can use the slider in the timeline to increase the actions selected and these will be shown in the Actions tab and all console logs and network logs will be filtered to only show the logs for the actions selected.
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ Double click on an action to see the time range for that action. You can use the
|
|||
|
||||
### Snapshots
|
||||
|
||||
When tracing with the [`option: snapshots`] option turned on (default), Playwright captures a set of complete DOM snapshots for each action. Depending on the type of the action, it will capture:
|
||||
When tracing with the [`option: Tracing.start.snapshots`] option turned on (default), Playwright captures a set of complete DOM snapshots for each action. Depending on the type of the action, it will capture:
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
|
|
@ -48,8 +48,6 @@ When tracing with the [`option: snapshots`] option turned on (default), Playwrig
|
|||
|Action|A snapshot at the moment of the performed input. This type of snapshot is especially useful when exploring where exactly Playwright clicked.|
|
||||
|After|A snapshot after the action.|
|
||||
|
||||
<br/>
|
||||
|
||||
Here is what the typical Action snapshot looks like:
|
||||
|
||||

|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
"browsers": [
|
||||
{
|
||||
"name": "chromium",
|
||||
"revision": "1136",
|
||||
"revision": "1137",
|
||||
"installByDefault": true,
|
||||
"browserVersion": "130.0.6723.6"
|
||||
"browserVersion": "130.0.6723.19"
|
||||
},
|
||||
{
|
||||
"name": "chromium-tip-of-tree",
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
},
|
||||
{
|
||||
"name": "webkit",
|
||||
"revision": "2082",
|
||||
"revision": "2083",
|
||||
"installByDefault": true,
|
||||
"revisionOverrides": {
|
||||
"mac10.14": "1446",
|
||||
|
|
|
|||
|
|
@ -478,8 +478,8 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
|||
return Response.fromNullable((await this._channel.goForward({ ...options, waitUntil })).response);
|
||||
}
|
||||
|
||||
async forceGarbageCollection() {
|
||||
await this._channel.forceGarbageCollection();
|
||||
async requestGC() {
|
||||
await this._channel.requestGC();
|
||||
}
|
||||
|
||||
async emulateMedia(options: { media?: 'screen' | 'print' | null, colorScheme?: 'dark' | 'light' | 'no-preference' | null, reducedMotion?: 'reduce' | 'no-preference' | null, forcedColors?: 'active' | 'none' | null } = {}) {
|
||||
|
|
|
|||
|
|
@ -1137,8 +1137,8 @@ scheme.PageGoForwardParams = tObject({
|
|||
scheme.PageGoForwardResult = tObject({
|
||||
response: tOptional(tChannel(['Response'])),
|
||||
});
|
||||
scheme.PageForceGarbageCollectionParams = tOptional(tObject({}));
|
||||
scheme.PageForceGarbageCollectionResult = tOptional(tObject({}));
|
||||
scheme.PageRequestGCParams = tOptional(tObject({}));
|
||||
scheme.PageRequestGCResult = tOptional(tObject({}));
|
||||
scheme.PageRegisterLocatorHandlerParams = tObject({
|
||||
selector: tString,
|
||||
noWaitAfter: tOptional(tBoolean),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import type { ConnectionTransport } from '../transport';
|
|||
import type * as types from '../types';
|
||||
import { BidiBrowser } from './bidiBrowser';
|
||||
import { kBrowserCloseMessageId } from './bidiConnection';
|
||||
import { createProfile } from './third_party/firefoxPrefs';
|
||||
|
||||
export class BidiFirefox extends BrowserType {
|
||||
constructor(parent: SdkObject) {
|
||||
|
|
@ -72,6 +73,13 @@ export class BidiFirefox extends BrowserType {
|
|||
transport.send({ method: 'browser.close', params: {}, id: kBrowserCloseMessageId });
|
||||
}
|
||||
|
||||
override async prepareUserDataDir(options: types.LaunchOptions, userDataDir: string): Promise<void> {
|
||||
await createProfile({
|
||||
path: userDataDir,
|
||||
preferences: options.firefoxUserPrefs || {},
|
||||
});
|
||||
}
|
||||
|
||||
override defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[] {
|
||||
const { args = [], headless } = options;
|
||||
const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile'));
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ export class BidiPage implements PageDelegate {
|
|||
}).then(() => true).catch(() => false);
|
||||
}
|
||||
|
||||
async forceGarbageCollection(): Promise<void> {
|
||||
async requestGC(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
|
|
|
|||
282
packages/playwright-core/src/server/bidi/third_party/firefoxPrefs.ts
vendored
Normal file
282
packages/playwright-core/src/server/bidi/third_party/firefoxPrefs.ts
vendored
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
/* eslint-disable curly, indent */
|
||||
|
||||
interface ProfileOptions {
|
||||
preferences: Record<string, unknown>;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export async function createProfile(options: ProfileOptions): Promise<void> {
|
||||
if (!fs.existsSync(options.path)) {
|
||||
await fs.promises.mkdir(options.path, {
|
||||
recursive: true,
|
||||
});
|
||||
}
|
||||
await writePreferences({
|
||||
preferences: {
|
||||
...defaultProfilePreferences(options.preferences),
|
||||
...options.preferences,
|
||||
},
|
||||
path: options.path,
|
||||
});
|
||||
}
|
||||
|
||||
function defaultProfilePreferences(
|
||||
extraPrefs: Record<string, unknown>
|
||||
): Record<string, unknown> {
|
||||
const server = 'dummy.test';
|
||||
|
||||
const defaultPrefs = {
|
||||
// Make sure Shield doesn't hit the network.
|
||||
'app.normandy.api_url': '',
|
||||
// Disable Firefox old build background check
|
||||
'app.update.checkInstallTime': false,
|
||||
// Disable automatically upgrading Firefox
|
||||
'app.update.disabledForTesting': true,
|
||||
|
||||
// Increase the APZ content response timeout to 1 minute
|
||||
'apz.content_response_timeout': 60000,
|
||||
|
||||
// Prevent various error message on the console
|
||||
// jest-puppeteer asserts that no error message is emitted by the console
|
||||
'browser.contentblocking.features.standard':
|
||||
'-tp,tpPrivate,cookieBehavior0,-cm,-fp',
|
||||
|
||||
// Enable the dump function: which sends messages to the system
|
||||
// console
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1543115
|
||||
'browser.dom.window.dump.enabled': true,
|
||||
// Disable topstories
|
||||
'browser.newtabpage.activity-stream.feeds.system.topstories': false,
|
||||
// Always display a blank page
|
||||
'browser.newtabpage.enabled': false,
|
||||
// Background thumbnails in particular cause grief: and disabling
|
||||
// thumbnails in general cannot hurt
|
||||
'browser.pagethumbnails.capturing_disabled': true,
|
||||
|
||||
// Disable safebrowsing components.
|
||||
'browser.safebrowsing.blockedURIs.enabled': false,
|
||||
'browser.safebrowsing.downloads.enabled': false,
|
||||
'browser.safebrowsing.malware.enabled': false,
|
||||
'browser.safebrowsing.phishing.enabled': false,
|
||||
|
||||
// Disable updates to search engines.
|
||||
'browser.search.update': false,
|
||||
// Do not restore the last open set of tabs if the browser has crashed
|
||||
'browser.sessionstore.resume_from_crash': false,
|
||||
// Skip check for default browser on startup
|
||||
'browser.shell.checkDefaultBrowser': false,
|
||||
|
||||
// Disable newtabpage
|
||||
'browser.startup.homepage': 'about:blank',
|
||||
// Do not redirect user when a milstone upgrade of Firefox is detected
|
||||
'browser.startup.homepage_override.mstone': 'ignore',
|
||||
// Start with a blank page about:blank
|
||||
'browser.startup.page': 0,
|
||||
|
||||
// Do not allow background tabs to be zombified on Android: otherwise for
|
||||
// tests that open additional tabs: the test harness tab itself might get
|
||||
// unloaded
|
||||
'browser.tabs.disableBackgroundZombification': false,
|
||||
// Do not warn when closing all other open tabs
|
||||
'browser.tabs.warnOnCloseOtherTabs': false,
|
||||
// Do not warn when multiple tabs will be opened
|
||||
'browser.tabs.warnOnOpen': false,
|
||||
|
||||
// Do not automatically offer translations, as tests do not expect this.
|
||||
'browser.translations.automaticallyPopup': false,
|
||||
|
||||
// Disable the UI tour.
|
||||
'browser.uitour.enabled': false,
|
||||
// Turn off search suggestions in the location bar so as not to trigger
|
||||
// network connections.
|
||||
'browser.urlbar.suggest.searches': false,
|
||||
// Disable first run splash page on Windows 10
|
||||
'browser.usedOnWindows10.introURL': '',
|
||||
// Do not warn on quitting Firefox
|
||||
'browser.warnOnQuit': false,
|
||||
|
||||
// Defensively disable data reporting systems
|
||||
'datareporting.healthreport.documentServerURI': `http://${server}/dummy/healthreport/`,
|
||||
'datareporting.healthreport.logging.consoleEnabled': false,
|
||||
'datareporting.healthreport.service.enabled': false,
|
||||
'datareporting.healthreport.service.firstRun': false,
|
||||
'datareporting.healthreport.uploadEnabled': false,
|
||||
|
||||
// Do not show datareporting policy notifications which can interfere with tests
|
||||
'datareporting.policy.dataSubmissionEnabled': false,
|
||||
'datareporting.policy.dataSubmissionPolicyBypassNotification': true,
|
||||
|
||||
// DevTools JSONViewer sometimes fails to load dependencies with its require.js.
|
||||
// This doesn't affect Puppeteer but spams console (Bug 1424372)
|
||||
'devtools.jsonview.enabled': false,
|
||||
|
||||
// Disable popup-blocker
|
||||
'dom.disable_open_during_load': false,
|
||||
|
||||
// Enable the support for File object creation in the content process
|
||||
// Required for |Page.setFileInputFiles| protocol method.
|
||||
'dom.file.createInChild': true,
|
||||
|
||||
// Disable the ProcessHangMonitor
|
||||
'dom.ipc.reportProcessHangs': false,
|
||||
|
||||
// Disable slow script dialogues
|
||||
'dom.max_chrome_script_run_time': 0,
|
||||
'dom.max_script_run_time': 0,
|
||||
|
||||
// Only load extensions from the application and user profile
|
||||
// AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
|
||||
'extensions.autoDisableScopes': 0,
|
||||
'extensions.enabledScopes': 5,
|
||||
|
||||
// Disable metadata caching for installed add-ons by default
|
||||
'extensions.getAddons.cache.enabled': false,
|
||||
|
||||
// Disable installing any distribution extensions or add-ons.
|
||||
'extensions.installDistroAddons': false,
|
||||
|
||||
// Disabled screenshots extension
|
||||
'extensions.screenshots.disabled': true,
|
||||
|
||||
// Turn off extension updates so they do not bother tests
|
||||
'extensions.update.enabled': false,
|
||||
|
||||
// Turn off extension updates so they do not bother tests
|
||||
'extensions.update.notifyUser': false,
|
||||
|
||||
// Make sure opening about:addons will not hit the network
|
||||
'extensions.webservice.discoverURL': `http://${server}/dummy/discoveryURL`,
|
||||
|
||||
// Allow the application to have focus even it runs in the background
|
||||
'focusmanager.testmode': true,
|
||||
|
||||
// Disable useragent updates
|
||||
'general.useragent.updates.enabled': false,
|
||||
|
||||
// Always use network provider for geolocation tests so we bypass the
|
||||
// macOS dialog raised by the corelocation provider
|
||||
'geo.provider.testing': true,
|
||||
|
||||
// Do not scan Wifi
|
||||
'geo.wifi.scan': false,
|
||||
|
||||
// No hang monitor
|
||||
'hangmonitor.timeout': 0,
|
||||
|
||||
// Show chrome errors and warnings in the error console
|
||||
'javascript.options.showInConsole': true,
|
||||
|
||||
// Disable download and usage of OpenH264: and Widevine plugins
|
||||
'media.gmp-manager.updateEnabled': false,
|
||||
|
||||
// Disable the GFX sanity window
|
||||
'media.sanity-test.disabled': true,
|
||||
|
||||
// Disable experimental feature that is only available in Nightly
|
||||
'network.cookie.sameSite.laxByDefault': false,
|
||||
|
||||
// Do not prompt for temporary redirects
|
||||
'network.http.prompt-temp-redirect': false,
|
||||
|
||||
// Disable speculative connections so they are not reported as leaking
|
||||
// when they are hanging around
|
||||
'network.http.speculative-parallel-limit': 0,
|
||||
|
||||
// Do not automatically switch between offline and online
|
||||
'network.manage-offline-status': false,
|
||||
|
||||
// Make sure SNTP requests do not hit the network
|
||||
'network.sntp.pools': server,
|
||||
|
||||
// Disable Flash.
|
||||
'plugin.state.flash': 0,
|
||||
|
||||
'privacy.trackingprotection.enabled': false,
|
||||
|
||||
// Can be removed once Firefox 89 is no longer supported
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1710839
|
||||
'remote.enabled': true,
|
||||
|
||||
// Don't do network connections for mitm priming
|
||||
'security.certerrors.mitm.priming.enabled': false,
|
||||
|
||||
// Local documents have access to all other local documents,
|
||||
// including directory listings
|
||||
'security.fileuri.strict_origin_policy': false,
|
||||
|
||||
// Do not wait for the notification button security delay
|
||||
'security.notification_enable_delay': 0,
|
||||
|
||||
// Ensure blocklist updates do not hit the network
|
||||
'services.settings.server': `http://${server}/dummy/blocklist/`,
|
||||
|
||||
// Do not automatically fill sign-in forms with known usernames and
|
||||
// passwords
|
||||
'signon.autofillForms': false,
|
||||
|
||||
// Disable password capture, so that tests that include forms are not
|
||||
// influenced by the presence of the persistent doorhanger notification
|
||||
'signon.rememberSignons': false,
|
||||
|
||||
// Disable first-run welcome page
|
||||
'startup.homepage_welcome_url': 'about:blank',
|
||||
|
||||
// Disable first-run welcome page
|
||||
'startup.homepage_welcome_url.additional': '',
|
||||
|
||||
// Disable browser animations (tabs, fullscreen, sliding alerts)
|
||||
'toolkit.cosmeticAnimations.enabled': false,
|
||||
|
||||
// Prevent starting into safe mode after application crashes
|
||||
'toolkit.startup.max_resumed_crashes': -1,
|
||||
};
|
||||
|
||||
return Object.assign(defaultPrefs, extraPrefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the user.js file with custom preferences as needed to allow
|
||||
* Firefox's CDP support to properly function. These preferences will be
|
||||
* automatically copied over to prefs.js during startup of Firefox. To be
|
||||
* able to restore the original values of preferences a backup of prefs.js
|
||||
* will be created.
|
||||
*
|
||||
* @param prefs - List of preferences to add.
|
||||
* @param profilePath - Firefox profile to write the preferences to.
|
||||
*/
|
||||
async function writePreferences(options: ProfileOptions): Promise<void> {
|
||||
const prefsPath = path.join(options.path, 'prefs.js');
|
||||
const lines = Object.entries(options.preferences).map(([key, value]) => {
|
||||
return `user_pref(${JSON.stringify(key)}, ${JSON.stringify(value)});`;
|
||||
});
|
||||
|
||||
// Use allSettled to prevent corruption
|
||||
const result = await Promise.allSettled([
|
||||
fs.promises.writeFile(path.join(options.path, 'user.js'), lines.join('\n')),
|
||||
// Create a backup of the preferences file if it already exitsts.
|
||||
fs.promises.access(prefsPath, fs.constants.F_OK).then(
|
||||
async () => {
|
||||
await fs.promises.copyFile(
|
||||
prefsPath,
|
||||
path.join(options.path, 'prefs.js.playwright')
|
||||
);
|
||||
},
|
||||
// Swallow only if file does not exist
|
||||
() => {}
|
||||
),
|
||||
]);
|
||||
for (const command of result) {
|
||||
if (command.status === 'rejected') {
|
||||
throw command.reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -192,6 +192,7 @@ export abstract class BrowserType extends SdkObject {
|
|||
userDataDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), `playwright_${this._name}dev_profile-`));
|
||||
tempDirectories.push(userDataDir);
|
||||
}
|
||||
await this.prepareUserDataDir(options, userDataDir);
|
||||
|
||||
const browserArguments = [];
|
||||
if (ignoreAllDefaultArgs)
|
||||
|
|
@ -328,6 +329,9 @@ export abstract class BrowserType extends SdkObject {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
async prepareUserDataDir(options: types.LaunchOptions, userDataDir: string): Promise<void> {
|
||||
}
|
||||
|
||||
abstract defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[];
|
||||
abstract connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise<Browser>;
|
||||
abstract amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env;
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ export class CRPage implements PageDelegate {
|
|||
return this._go(+1);
|
||||
}
|
||||
|
||||
async forceGarbageCollection(): Promise<void> {
|
||||
async requestGC(): Promise<void> {
|
||||
await this._mainFrameSession._client.send('HeapProfiler.collectGarbage');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 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/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 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/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 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/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 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/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 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/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 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/130.0.6723.6 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 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/130.0.6723.6 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 1138,
|
||||
"height": 712
|
||||
|
|
@ -1098,7 +1098,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 384,
|
||||
"height": 640
|
||||
|
|
@ -1109,7 +1109,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 384
|
||||
|
|
@ -1120,7 +1120,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1131,7 +1131,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1142,7 +1142,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1153,7 +1153,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36 Edge/14.14263",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1164,7 +1164,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/130.0.6723.6 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 800,
|
||||
"height": 1280
|
||||
|
|
@ -1175,7 +1175,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/130.0.6723.6 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
|
|
@ -1186,7 +1186,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/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 384,
|
||||
"height": 640
|
||||
|
|
@ -1197,7 +1197,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/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 384
|
||||
|
|
@ -1208,7 +1208,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Nexus 5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1219,7 +1219,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/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1230,7 +1230,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1241,7 +1241,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1252,7 +1252,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/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1263,7 +1263,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/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1274,7 +1274,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 412,
|
||||
"height": 732
|
||||
|
|
@ -1285,7 +1285,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 732,
|
||||
"height": 412
|
||||
|
|
@ -1296,7 +1296,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/130.0.6723.6 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 600,
|
||||
"height": 960
|
||||
|
|
@ -1307,7 +1307,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/130.0.6723.6 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 960,
|
||||
"height": 600
|
||||
|
|
@ -1362,7 +1362,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 411,
|
||||
"height": 731
|
||||
|
|
@ -1373,7 +1373,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 731,
|
||||
"height": 411
|
||||
|
|
@ -1384,7 +1384,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 411,
|
||||
"height": 823
|
||||
|
|
@ -1395,7 +1395,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 823,
|
||||
"height": 411
|
||||
|
|
@ -1406,7 +1406,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 393,
|
||||
"height": 786
|
||||
|
|
@ -1417,7 +1417,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/130.0.6723.6 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/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 786,
|
||||
"height": 393
|
||||
|
|
@ -1428,7 +1428,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 353,
|
||||
"height": 745
|
||||
|
|
@ -1439,7 +1439,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 745,
|
||||
"height": 353
|
||||
|
|
@ -1450,7 +1450,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4a (5G)": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 412,
|
||||
"height": 892
|
||||
|
|
@ -1465,7 +1465,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 4a (5G) landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"height": 892,
|
||||
"width": 412
|
||||
|
|
@ -1480,7 +1480,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 5": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 393,
|
||||
"height": 851
|
||||
|
|
@ -1495,7 +1495,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 5 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 851,
|
||||
"height": 393
|
||||
|
|
@ -1510,7 +1510,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 7": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 412,
|
||||
"height": 915
|
||||
|
|
@ -1525,7 +1525,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Pixel 7 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"screen": {
|
||||
"width": 915,
|
||||
"height": 412
|
||||
|
|
@ -1540,7 +1540,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Moto G4": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 360,
|
||||
"height": 640
|
||||
|
|
@ -1551,7 +1551,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Moto G4 landscape": {
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Mobile Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Mobile Safari/537.36",
|
||||
"viewport": {
|
||||
"width": 640,
|
||||
"height": 360
|
||||
|
|
@ -1562,7 +1562,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Chrome HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Safari/537.36",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1577,7 +1577,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Edge HiDPI": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Safari/537.36 Edg/130.0.6723.6",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Safari/537.36 Edg/130.0.6723.19",
|
||||
"screen": {
|
||||
"width": 1792,
|
||||
"height": 1120
|
||||
|
|
@ -1622,7 +1622,7 @@
|
|||
"defaultBrowserType": "webkit"
|
||||
},
|
||||
"Desktop Chrome": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Safari/537.36",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Safari/537.36",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
@ -1637,7 +1637,7 @@
|
|||
"defaultBrowserType": "chromium"
|
||||
},
|
||||
"Desktop Edge": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.6 Safari/537.36 Edg/130.0.6723.6",
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.19 Safari/537.36 Edg/130.0.6723.19",
|
||||
"screen": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
|
|
|
|||
|
|
@ -139,8 +139,8 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
|
|||
return { response: ResponseDispatcher.fromNullable(this.parentScope(), await this._page.goForward(metadata, params)) };
|
||||
}
|
||||
|
||||
async forceGarbageCollection(params: channels.PageForceGarbageCollectionParams, metadata: CallMetadata): Promise<channels.PageForceGarbageCollectionResult> {
|
||||
await this._page.forceGarbageCollection();
|
||||
async requestGC(params: channels.PageRequestGCParams, metadata: CallMetadata): Promise<channels.PageRequestGCResult> {
|
||||
await this._page.requestGC();
|
||||
}
|
||||
|
||||
async registerLocatorHandler(params: channels.PageRegisterLocatorHandlerParams, metadata: CallMetadata): Promise<channels.PageRegisterLocatorHandlerResult> {
|
||||
|
|
|
|||
|
|
@ -399,7 +399,7 @@ export class FFPage implements PageDelegate {
|
|||
return success;
|
||||
}
|
||||
|
||||
async forceGarbageCollection(): Promise<void> {
|
||||
async requestGC(): Promise<void> {
|
||||
await this._session.send('Heap.collectGarbage');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ class InspectTool implements RecorderTool {
|
|||
|
||||
class RecordActionTool implements RecorderTool {
|
||||
private _recorder: Recorder;
|
||||
private _performingAction: actions.PerformOnRecordAction | null = null;
|
||||
private _performingActions = new Set<actions.PerformOnRecordAction>();
|
||||
private _hoveredModel: HighlightModel | null = null;
|
||||
private _hoveredElement: HTMLElement | null = null;
|
||||
private _activeModel: HighlightModel | null = null;
|
||||
|
|
@ -333,21 +333,21 @@ class RecordActionTool implements RecorderTool {
|
|||
onPointerDown(event: PointerEvent) {
|
||||
if (this._shouldIgnoreMouseEvent(event))
|
||||
return;
|
||||
if (!this._performingAction)
|
||||
if (!this._performingActions.size)
|
||||
consumeEvent(event);
|
||||
}
|
||||
|
||||
onPointerUp(event: PointerEvent) {
|
||||
if (this._shouldIgnoreMouseEvent(event))
|
||||
return;
|
||||
if (!this._performingAction)
|
||||
if (!this._performingActions.size)
|
||||
consumeEvent(event);
|
||||
}
|
||||
|
||||
onMouseDown(event: MouseEvent) {
|
||||
if (this._shouldIgnoreMouseEvent(event))
|
||||
return;
|
||||
if (!this._performingAction)
|
||||
if (!this._performingActions.size)
|
||||
consumeEvent(event);
|
||||
this._activeModel = this._hoveredModel;
|
||||
}
|
||||
|
|
@ -355,7 +355,7 @@ class RecordActionTool implements RecorderTool {
|
|||
onMouseUp(event: MouseEvent) {
|
||||
if (this._shouldIgnoreMouseEvent(event))
|
||||
return;
|
||||
if (!this._performingAction)
|
||||
if (!this._performingActions.size)
|
||||
consumeEvent(event);
|
||||
}
|
||||
|
||||
|
|
@ -509,12 +509,13 @@ class RecordActionTool implements RecorderTool {
|
|||
private _actionInProgress(event: Event): boolean {
|
||||
// If Playwright is performing action for us, bail.
|
||||
const isKeyEvent = event instanceof KeyboardEvent;
|
||||
if (this._performingAction?.name === 'press' && isKeyEvent && event.key === this._performingAction.key)
|
||||
return true;
|
||||
|
||||
const isMouseOrPointerEvent = event instanceof MouseEvent || event instanceof PointerEvent;
|
||||
if (isMouseOrPointerEvent && (this._performingAction?.name === 'click' || this._performingAction?.name === 'check' || this._performingAction?.name === 'uncheck'))
|
||||
return true;
|
||||
for (const action of this._performingActions) {
|
||||
if (isKeyEvent && action.name === 'press' && event.key === action.key)
|
||||
return true;
|
||||
if (isMouseOrPointerEvent && (action.name === 'click' || action.name === 'check' || action.name === 'uncheck'))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Consume event if action is not being executed.
|
||||
consumeEvent(event);
|
||||
|
|
@ -540,9 +541,9 @@ class RecordActionTool implements RecorderTool {
|
|||
this._hoveredModel = null;
|
||||
this._activeModel = null;
|
||||
this._recorder.updateHighlight(null, false);
|
||||
this._performingAction = action;
|
||||
this._performingActions.add(action);
|
||||
void this._recorder.performAction(action).then(() => {
|
||||
this._performingAction = null;
|
||||
this._performingActions.delete(action);
|
||||
|
||||
// If that was a keyboard action, it similarly requires new selectors for active model.
|
||||
this._onFocus(false);
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export interface PageDelegate {
|
|||
reload(): Promise<void>;
|
||||
goBack(): Promise<boolean>;
|
||||
goForward(): Promise<boolean>;
|
||||
forceGarbageCollection(): Promise<void>;
|
||||
requestGC(): Promise<void>;
|
||||
addInitScript(initScript: InitScript): Promise<void>;
|
||||
removeNonInternalInitScripts(): Promise<void>;
|
||||
closePage(runBeforeUnload: boolean): Promise<void>;
|
||||
|
|
@ -431,8 +431,8 @@ export class Page extends SdkObject {
|
|||
}), this._timeoutSettings.navigationTimeout(options));
|
||||
}
|
||||
|
||||
forceGarbageCollection(): Promise<void> {
|
||||
return this._delegate.forceGarbageCollection();
|
||||
requestGC(): Promise<void> {
|
||||
return this._delegate.requestGC();
|
||||
}
|
||||
|
||||
registerLocatorHandler(selector: string, noWaitAfter: boolean | undefined) {
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@ const DOWNLOAD_PATHS: Record<BrowserName | InternalTool, DownloadPaths> = {
|
|||
'mac13-arm64': 'builds/chromium/%s/chromium-mac-arm64.zip',
|
||||
'mac14': 'builds/chromium/%s/chromium-mac.zip',
|
||||
'mac14-arm64': 'builds/chromium/%s/chromium-mac-arm64.zip',
|
||||
'mac15': 'builds/chromium/%s/chromium-mac.zip',
|
||||
'mac15-arm64': 'builds/chromium/%s/chromium-mac-arm64.zip',
|
||||
'win64': 'builds/chromium/%s/chromium-win64.zip',
|
||||
},
|
||||
'chromium-tip-of-tree': {
|
||||
|
|
@ -127,6 +129,8 @@ const DOWNLOAD_PATHS: Record<BrowserName | InternalTool, DownloadPaths> = {
|
|||
'mac13-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip',
|
||||
'mac14': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
|
||||
'mac14-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip',
|
||||
'mac15': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
|
||||
'mac15-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip',
|
||||
'win64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-win64.zip',
|
||||
},
|
||||
'firefox': {
|
||||
|
|
@ -154,6 +158,8 @@ const DOWNLOAD_PATHS: Record<BrowserName | InternalTool, DownloadPaths> = {
|
|||
'mac13-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
|
||||
'mac14': 'builds/firefox/%s/firefox-mac.zip',
|
||||
'mac14-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
|
||||
'mac15': 'builds/firefox/%s/firefox-mac.zip',
|
||||
'mac15-arm64': 'builds/firefox/%s/firefox-mac-arm64.zip',
|
||||
'win64': 'builds/firefox/%s/firefox-win64.zip',
|
||||
},
|
||||
'firefox-beta': {
|
||||
|
|
@ -181,6 +187,8 @@ const DOWNLOAD_PATHS: Record<BrowserName | InternalTool, DownloadPaths> = {
|
|||
'mac13-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
|
||||
'mac14': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
|
||||
'mac14-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
|
||||
'mac15': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
|
||||
'mac15-arm64': 'builds/firefox-beta/%s/firefox-beta-mac-arm64.zip',
|
||||
'win64': 'builds/firefox-beta/%s/firefox-beta-win64.zip',
|
||||
},
|
||||
'webkit': {
|
||||
|
|
@ -208,6 +216,8 @@ const DOWNLOAD_PATHS: Record<BrowserName | InternalTool, DownloadPaths> = {
|
|||
'mac13-arm64': 'builds/webkit/%s/webkit-mac-13-arm64.zip',
|
||||
'mac14': 'builds/webkit/%s/webkit-mac-14.zip',
|
||||
'mac14-arm64': 'builds/webkit/%s/webkit-mac-14-arm64.zip',
|
||||
'mac15': 'builds/webkit/%s/webkit-mac-15.zip',
|
||||
'mac15-arm64': 'builds/webkit/%s/webkit-mac-15-arm64.zip',
|
||||
'win64': 'builds/webkit/%s/webkit-win64.zip',
|
||||
},
|
||||
'ffmpeg': {
|
||||
|
|
@ -235,6 +245,8 @@ const DOWNLOAD_PATHS: Record<BrowserName | InternalTool, DownloadPaths> = {
|
|||
'mac13-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
|
||||
'mac14': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
|
||||
'mac14-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
|
||||
'mac15': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
|
||||
'mac15-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip',
|
||||
'win64': 'builds/ffmpeg/%s/ffmpeg-win64.zip',
|
||||
},
|
||||
'android': {
|
||||
|
|
@ -262,6 +274,8 @@ const DOWNLOAD_PATHS: Record<BrowserName | InternalTool, DownloadPaths> = {
|
|||
'mac13-arm64': 'builds/android/%s/android.zip',
|
||||
'mac14': 'builds/android/%s/android.zip',
|
||||
'mac14-arm64': 'builds/android/%s/android.zip',
|
||||
'mac15': 'builds/android/%s/android.zip',
|
||||
'mac15-arm64': 'builds/android/%s/android.zip',
|
||||
'win64': 'builds/android/%s/android.zip',
|
||||
},
|
||||
// TODO(bidi): implement downloads.
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
import type { BrowserContext } from '../../browserContext';
|
||||
import type { Page } from '../../page';
|
||||
import type { FrameSnapshot } from '@trace/snapshot';
|
||||
import type { SnapshotRenderer } from '../../../../../trace-viewer/src/snapshotRenderer';
|
||||
import { SnapshotStorage } from '../../../../../trace-viewer/src/snapshotStorage';
|
||||
import type { SnapshotRenderer } from '../../../../../trace-viewer/src/sw/snapshotRenderer';
|
||||
import { SnapshotStorage } from '../../../../../trace-viewer/src/sw/snapshotStorage';
|
||||
import type { SnapshotterBlob, SnapshotterDelegate } from '../recorder/snapshotter';
|
||||
import { Snapshotter } from '../recorder/snapshotter';
|
||||
import type { ElementHandle } from '../../dom';
|
||||
|
|
|
|||
|
|
@ -40,13 +40,9 @@ export type TraceViewerRedirectOptions = {
|
|||
grep?: string;
|
||||
grepInvert?: string;
|
||||
project?: string[];
|
||||
workers?: number | string;
|
||||
headed?: boolean;
|
||||
timeout?: number;
|
||||
reporter?: string[];
|
||||
webApp?: string;
|
||||
isServer?: boolean;
|
||||
updateSnapshots?: 'all' | 'none' | 'missing';
|
||||
};
|
||||
|
||||
export type TraceViewerAppOptions = {
|
||||
|
|
@ -126,14 +122,6 @@ export async function installRootRedirect(server: HttpServer, traceUrls: string[
|
|||
params.append('grepInvert', options.grepInvert);
|
||||
for (const project of options.project || [])
|
||||
params.append('project', project);
|
||||
if (options.workers)
|
||||
params.append('workers', String(options.workers));
|
||||
if (options.timeout)
|
||||
params.append('timeout', String(options.timeout));
|
||||
if (options.headed)
|
||||
params.append('headed', '');
|
||||
if (options.updateSnapshots)
|
||||
params.append('updateSnapshots', options.updateSnapshots);
|
||||
for (const reporter of options.reporter || [])
|
||||
params.append('reporter', reporter);
|
||||
|
||||
|
|
|
|||
|
|
@ -767,7 +767,7 @@ export class WKPage implements PageDelegate {
|
|||
});
|
||||
}
|
||||
|
||||
async forceGarbageCollection(): Promise<void> {
|
||||
async requestGC(): Promise<void> {
|
||||
await this._session.send('Heap.gc');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export type HostPlatform = 'win64' |
|
|||
'mac12' | 'mac12-arm64' |
|
||||
'mac13' | 'mac13-arm64' |
|
||||
'mac14' | 'mac14-arm64' |
|
||||
'mac15' | 'mac15-arm64' |
|
||||
'ubuntu18.04-x64' | 'ubuntu18.04-arm64' |
|
||||
'ubuntu20.04-x64' | 'ubuntu20.04-arm64' |
|
||||
'ubuntu22.04-x64' | 'ubuntu22.04-arm64' |
|
||||
|
|
@ -47,9 +48,9 @@ function calculatePlatform(): { hostPlatform: HostPlatform, isOfficiallySupporte
|
|||
macVersion = 'mac10.15';
|
||||
} else {
|
||||
// ver[0] >= 20
|
||||
const LAST_STABLE_MAC_MAJOR_VERSION = 14;
|
||||
const LAST_STABLE_MACOS_MAJOR_VERSION = 15;
|
||||
// Best-effort support for MacOS beta versions.
|
||||
macVersion = 'mac' + Math.min(ver[0] - 9, LAST_STABLE_MAC_MAJOR_VERSION);
|
||||
macVersion = 'mac' + Math.min(ver[0] - 9, LAST_STABLE_MACOS_MAJOR_VERSION);
|
||||
// BigSur is the first version that might run on Apple Silicon.
|
||||
if (os.cpus().some(cpu => cpu.model.includes('Apple')))
|
||||
macVersion += '-arm64';
|
||||
|
|
|
|||
2668
packages/playwright-core/types/types.d.ts
vendored
2668
packages/playwright-core/types/types.d.ts
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -94,7 +94,6 @@ export interface TestServerInterface {
|
|||
testIds?: string[];
|
||||
headed?: boolean;
|
||||
workers?: number | string;
|
||||
timeout?: number,
|
||||
updateSnapshots?: 'all' | 'none' | 'missing';
|
||||
reporters?: string[],
|
||||
trace?: 'on' | 'off';
|
||||
|
|
|
|||
|
|
@ -168,11 +168,7 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
|
|||
grep: opts.grep as string | undefined,
|
||||
grepInvert: opts.grepInvert as string | undefined,
|
||||
project: opts.project || undefined,
|
||||
headed: opts.headed,
|
||||
reporter: Array.isArray(opts.reporter) ? opts.reporter : opts.reporter ? [opts.reporter] : undefined,
|
||||
workers: cliOverrides.workers,
|
||||
timeout: cliOverrides.timeout,
|
||||
updateSnapshots: cliOverrides.updateSnapshots,
|
||||
});
|
||||
await stopProfiling('runner');
|
||||
if (status === 'restarted')
|
||||
|
|
|
|||
|
|
@ -300,17 +300,17 @@ export class TestServerDispatcher implements TestServerInterface {
|
|||
repeatEach: 1,
|
||||
retries: 0,
|
||||
preserveOutputDir: true,
|
||||
timeout: params.timeout,
|
||||
reporter: params.reporters ? params.reporters.map(r => [r]) : undefined,
|
||||
use: {
|
||||
...(this._configCLIOverrides.use || {}),
|
||||
trace: params.trace === 'on' ? { mode: 'on', sources: false, _live: true } : (params.trace === 'off' ? 'off' : undefined),
|
||||
video: params.video === 'on' ? 'on' : (params.video === 'off' ? 'off' : undefined),
|
||||
headless: params.headed ? false : undefined,
|
||||
_optionContextReuseMode: params.reuseContext ? 'when-possible' : undefined,
|
||||
_optionConnectOptions: params.connectWsEndpoint ? { wsEndpoint: params.connectWsEndpoint } : undefined,
|
||||
},
|
||||
updateSnapshots: params.updateSnapshots,
|
||||
workers: params.workers,
|
||||
...(params.updateSnapshots ? { updateSnapshots: params.updateSnapshots } : {}),
|
||||
...(params.workers ? { workers: params.workers } : {}),
|
||||
};
|
||||
if (params.trace === 'on')
|
||||
process.env.PW_LIVE_TRACE_STACKS = '1';
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export class WorkerHost extends ProcessHost {
|
|||
super(require.resolve('../worker/workerMain.js'), `worker-${workerIndex}`, {
|
||||
...extraEnv,
|
||||
FORCE_COLOR: '1',
|
||||
DEBUG_COLORS: '1',
|
||||
DEBUG_COLORS: process.env.DEBUG_COLORS === undefined ? '1' : process.env.DEBUG_COLORS,
|
||||
});
|
||||
this.workerIndex = workerIndex;
|
||||
this.parallelIndex = parallelIndex;
|
||||
|
|
|
|||
411
packages/playwright/types/test.d.ts
vendored
411
packages/playwright/types/test.d.ts
vendored
File diff suppressed because it is too large
Load diff
45
packages/playwright/types/testReporter.d.ts
vendored
45
packages/playwright/types/testReporter.d.ts
vendored
|
|
@ -94,18 +94,20 @@ export interface FullResult {
|
|||
*
|
||||
* Here is a typical order of reporter calls:
|
||||
* - [reporter.onBegin(config, suite)](https://playwright.dev/docs/api/class-reporter#reporter-on-begin) is called
|
||||
* once with a root suite that contains all other suites and tests. Learn more about [suites hierarchy]{@link
|
||||
* Suite}.
|
||||
* once with a root suite that contains all other suites and tests. Learn more about
|
||||
* [suites hierarchy][Suite](https://playwright.dev/docs/api/class-suite).
|
||||
* - [reporter.onTestBegin(test, result)](https://playwright.dev/docs/api/class-reporter#reporter-on-test-begin) is
|
||||
* called for each test run. It is given a {@link TestCase} that is executed, and a {@link TestResult} that is
|
||||
* almost empty. Test result will be populated while the test runs (for example, with steps and stdio) and will
|
||||
* get final `status` once the test finishes.
|
||||
* called for each test run. It is given a [TestCase](https://playwright.dev/docs/api/class-testcase) that is
|
||||
* executed, and a [TestResult](https://playwright.dev/docs/api/class-testresult) that is almost empty. Test
|
||||
* result will be populated while the test runs (for example, with steps and stdio) and will get final `status`
|
||||
* once the test finishes.
|
||||
* - [reporter.onStepBegin(test, result, step)](https://playwright.dev/docs/api/class-reporter#reporter-on-step-begin)
|
||||
* and
|
||||
* [reporter.onStepEnd(test, result, step)](https://playwright.dev/docs/api/class-reporter#reporter-on-step-end)
|
||||
* are called for each executed step inside the test. When steps are executed, test run has not finished yet.
|
||||
* - [reporter.onTestEnd(test, result)](https://playwright.dev/docs/api/class-reporter#reporter-on-test-end) is
|
||||
* called when test run has finished. By this time, {@link TestResult} is complete and you can use
|
||||
* called when test run has finished. By this time, [TestResult](https://playwright.dev/docs/api/class-testresult)
|
||||
* is complete and you can use
|
||||
* [testResult.status](https://playwright.dev/docs/api/class-testresult#test-result-status),
|
||||
* [testResult.error](https://playwright.dev/docs/api/class-testresult#test-result-error) and more.
|
||||
* - [reporter.onEnd(result)](https://playwright.dev/docs/api/class-reporter#reporter-on-end) is called once after
|
||||
|
|
@ -128,12 +130,13 @@ export interface FullResult {
|
|||
* **Merged report API notes**
|
||||
*
|
||||
* When merging multiple [`blob`](https://playwright.dev/docs/test-reporters#blob-reporter) reports via
|
||||
* [`merge-reports`](https://playwright.dev/docs/test-sharding#merge-reports-cli) CLI command, the same {@link Reporter} API is called to
|
||||
* produce final reports and all existing reporters should work without any changes. There some subtle differences
|
||||
* though which might affect some custom reporters.
|
||||
* - Projects from different shards are always kept as separate {@link TestProject} objects. E.g. if project
|
||||
* 'Desktop Chrome' was sharded across 5 machines then there will be 5 instances of projects with the same name in
|
||||
* the config passed to
|
||||
* [`merge-reports`](https://playwright.dev/docs/test-sharding#merge-reports-cli) CLI command, the same
|
||||
* [Reporter](https://playwright.dev/docs/api/class-reporter) API is called to produce final reports and all existing
|
||||
* reporters should work without any changes. There some subtle differences though which might affect some custom
|
||||
* reporters.
|
||||
* - Projects from different shards are always kept as separate
|
||||
* [TestProject](https://playwright.dev/docs/api/class-testproject) objects. E.g. if project 'Desktop Chrome' was
|
||||
* sharded across 5 machines then there will be 5 instances of projects with the same name in the config passed to
|
||||
* [reporter.onBegin(config, suite)](https://playwright.dev/docs/api/class-reporter#reporter-on-begin).
|
||||
*/
|
||||
export interface Reporter {
|
||||
|
|
@ -151,8 +154,8 @@ export interface Reporter {
|
|||
*/
|
||||
onEnd?(result: FullResult): Promise<{ status?: FullResult['status'] } | undefined | void> | void;
|
||||
/**
|
||||
* Called once before running tests. All tests have been already discovered and put into a hierarchy of {@link
|
||||
* Suite}s.
|
||||
* Called once before running tests. All tests have been already discovered and put into a hierarchy of
|
||||
* [Suite](https://playwright.dev/docs/api/class-suite)s.
|
||||
* @param config Resolved configuration.
|
||||
* @param suite The root suite that contains all projects, files and test cases.
|
||||
*/
|
||||
|
|
@ -321,16 +324,16 @@ export {};
|
|||
|
||||
/**
|
||||
* `Suite` is a group of tests. All tests in Playwright Test form the following hierarchy:
|
||||
* - Root suite has a child suite for each {@link FullProject}.
|
||||
* - Root suite has a child suite for each [FullProject](https://playwright.dev/docs/api/class-fullproject).
|
||||
* - Project suite #1. Has a child suite for each test file in the project.
|
||||
* - File suite #1
|
||||
* - {@link TestCase} #1
|
||||
* - {@link TestCase} #2
|
||||
* - [TestCase](https://playwright.dev/docs/api/class-testcase) #1
|
||||
* - [TestCase](https://playwright.dev/docs/api/class-testcase) #2
|
||||
* - Suite corresponding to a
|
||||
* [test.describe([title, details, callback])](https://playwright.dev/docs/api/class-test#test-describe)
|
||||
* group
|
||||
* - {@link TestCase} #1 in a group
|
||||
* - {@link TestCase} #2 in a group
|
||||
* - [TestCase](https://playwright.dev/docs/api/class-testcase) #1 in a group
|
||||
* - [TestCase](https://playwright.dev/docs/api/class-testcase) #2 in a group
|
||||
* - < more test cases ... >
|
||||
* - File suite #2
|
||||
* - < more file suites ... >
|
||||
|
|
@ -376,7 +379,7 @@ export interface Suite {
|
|||
parent?: Suite;
|
||||
|
||||
/**
|
||||
* Child suites. See {@link Suite} for the hierarchy of suites.
|
||||
* Child suites. See [Suite](https://playwright.dev/docs/api/class-suite) for the hierarchy of suites.
|
||||
*/
|
||||
suites: Array<Suite>;
|
||||
|
||||
|
|
@ -578,7 +581,7 @@ export interface TestError {
|
|||
}
|
||||
|
||||
/**
|
||||
* A result of a single {@link TestCase} run.
|
||||
* A result of a single [TestCase](https://playwright.dev/docs/api/class-testcase) run.
|
||||
*/
|
||||
export interface TestResult {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1956,7 +1956,7 @@ export interface PageChannel extends PageEventTarget, EventTargetChannel {
|
|||
exposeBinding(params: PageExposeBindingParams, metadata?: CallMetadata): Promise<PageExposeBindingResult>;
|
||||
goBack(params: PageGoBackParams, metadata?: CallMetadata): Promise<PageGoBackResult>;
|
||||
goForward(params: PageGoForwardParams, metadata?: CallMetadata): Promise<PageGoForwardResult>;
|
||||
forceGarbageCollection(params?: PageForceGarbageCollectionParams, metadata?: CallMetadata): Promise<PageForceGarbageCollectionResult>;
|
||||
requestGC(params?: PageRequestGCParams, metadata?: CallMetadata): Promise<PageRequestGCResult>;
|
||||
registerLocatorHandler(params: PageRegisterLocatorHandlerParams, metadata?: CallMetadata): Promise<PageRegisterLocatorHandlerResult>;
|
||||
resolveLocatorHandlerNoReply(params: PageResolveLocatorHandlerNoReplyParams, metadata?: CallMetadata): Promise<PageResolveLocatorHandlerNoReplyResult>;
|
||||
unregisterLocatorHandler(params: PageUnregisterLocatorHandlerParams, metadata?: CallMetadata): Promise<PageUnregisterLocatorHandlerResult>;
|
||||
|
|
@ -2098,9 +2098,9 @@ export type PageGoForwardOptions = {
|
|||
export type PageGoForwardResult = {
|
||||
response?: ResponseChannel,
|
||||
};
|
||||
export type PageForceGarbageCollectionParams = {};
|
||||
export type PageForceGarbageCollectionOptions = {};
|
||||
export type PageForceGarbageCollectionResult = void;
|
||||
export type PageRequestGCParams = {};
|
||||
export type PageRequestGCOptions = {};
|
||||
export type PageRequestGCResult = void;
|
||||
export type PageRegisterLocatorHandlerParams = {
|
||||
selector: string,
|
||||
noWaitAfter?: boolean,
|
||||
|
|
|
|||
|
|
@ -1450,7 +1450,7 @@ Page:
|
|||
slowMo: true
|
||||
snapshot: true
|
||||
|
||||
forceGarbageCollection:
|
||||
requestGC:
|
||||
|
||||
registerLocatorHandler:
|
||||
parameters:
|
||||
|
|
|
|||
|
|
@ -3,3 +3,6 @@
|
|||
@trace/**
|
||||
@web/**
|
||||
ui/
|
||||
|
||||
[sw-main.ts]
|
||||
sw/**
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export class MultiMap<K, V> {
|
||||
private _map: Map<K, V[]>;
|
||||
|
||||
constructor() {
|
||||
this._map = new Map<K, V[]>();
|
||||
}
|
||||
|
||||
set(key: K, value: V) {
|
||||
let values = this._map.get(key);
|
||||
if (!values) {
|
||||
values = [];
|
||||
this._map.set(key, values);
|
||||
}
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
get(key: K): V[] {
|
||||
return this._map.get(key) || [];
|
||||
}
|
||||
|
||||
has(key: K): boolean {
|
||||
return this._map.has(key);
|
||||
}
|
||||
|
||||
delete(key: K, value: V) {
|
||||
const values = this._map.get(key);
|
||||
if (!values)
|
||||
return;
|
||||
if (values.includes(value))
|
||||
this._map.set(key, values.filter(v => value !== v));
|
||||
}
|
||||
|
||||
deleteAll(key: K) {
|
||||
this._map.delete(key);
|
||||
}
|
||||
|
||||
hasValue(key: K, value: V): boolean {
|
||||
const values = this._map.get(key);
|
||||
if (!values)
|
||||
return false;
|
||||
return values.includes(value);
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this._map.size;
|
||||
}
|
||||
|
||||
[Symbol.iterator](): Iterator<[K, V[]]> {
|
||||
return this._map[Symbol.iterator]();
|
||||
}
|
||||
|
||||
keys(): IterableIterator<K> {
|
||||
return this._map.keys();
|
||||
}
|
||||
|
||||
values(): Iterable<V> {
|
||||
const result: V[] = [];
|
||||
for (const key of this.keys())
|
||||
result.push(...this.get(key));
|
||||
return result;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._map.clear();
|
||||
}
|
||||
}
|
||||
17
packages/trace-viewer/src/sw-main.ts
Normal file
17
packages/trace-viewer/src/sw-main.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* 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 './sw/main';
|
||||
2
packages/trace-viewer/src/sw/DEPS.list
Normal file
2
packages/trace-viewer/src/sw/DEPS.list
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[*]
|
||||
@isomorphic/**
|
||||
|
|
@ -14,9 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { parseClientSideCallMetadata } from '../../../packages/playwright-core/src/utils/isomorphic/traceUtils';
|
||||
import type { ActionEntry, ContextEntry } from './entries';
|
||||
import { createEmptyContext } from './entries';
|
||||
import { parseClientSideCallMetadata } from '@isomorphic/traceUtils';
|
||||
import type { ActionEntry, ContextEntry } from '../types/entries';
|
||||
import { SnapshotStorage } from './snapshotStorage';
|
||||
import { TraceModernizer } from './traceModernizer';
|
||||
|
||||
|
|
@ -150,3 +149,26 @@ function collapseActionsForRecorder(actions: ActionEntry[]): ActionEntry[] {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function createEmptyContext(): ContextEntry {
|
||||
return {
|
||||
origin: 'testRunner',
|
||||
traceUrl: '',
|
||||
startTime: Number.MAX_SAFE_INTEGER,
|
||||
wallTime: Number.MAX_SAFE_INTEGER,
|
||||
endTime: 0,
|
||||
browserName: '',
|
||||
options: {
|
||||
deviceScaleFactor: 1,
|
||||
isMobile: false,
|
||||
viewport: { width: 1280, height: 800 },
|
||||
},
|
||||
pages: [],
|
||||
resources: [],
|
||||
actions: [],
|
||||
events: [],
|
||||
errors: [],
|
||||
stdio: [],
|
||||
hasSource: false,
|
||||
};
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ import type * as traceV3 from './versions/traceV3';
|
|||
import type * as traceV4 from './versions/traceV4';
|
||||
import type * as traceV5 from './versions/traceV5';
|
||||
import type * as traceV6 from './versions/traceV6';
|
||||
import type { ActionEntry, ContextEntry, PageEntry } from './entries';
|
||||
import type { ActionEntry, ContextEntry, PageEntry } from '../types/entries';
|
||||
import type { SnapshotStorage } from './snapshotStorage';
|
||||
|
||||
export class TraceVersionError extends Error {
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Entry as ResourceSnapshot } from '../../../trace/src/har';
|
||||
import type { Entry as ResourceSnapshot } from '@trace/har';
|
||||
|
||||
type SerializedValue = {
|
||||
n?: number,
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Entry as ResourceSnapshot } from '../../../trace/src/har';
|
||||
import type { Entry as ResourceSnapshot } from '@trace/har';
|
||||
|
||||
type Language = 'javascript' | 'python' | 'java' | 'csharp' | 'jsonl';
|
||||
type Point = { x: number, y: number };
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
|
||||
import type { Entry as ResourceSnapshot } from '../../../trace/src/har';
|
||||
import type { Entry as ResourceSnapshot } from '@trace/har';
|
||||
|
||||
type Language = 'javascript' | 'python' | 'java' | 'csharp' | 'jsonl';
|
||||
type Point = { x: number, y: number };
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Entry as ResourceSnapshot } from '../../../trace/src/har';
|
||||
import type { Entry as ResourceSnapshot } from '@trace/har';
|
||||
|
||||
type Language = 'javascript' | 'python' | 'java' | 'csharp' | 'jsonl';
|
||||
type Point = { x: number, y: number };
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Language } from '../../playwright-core/src/utils/isomorphic/locatorGenerators';
|
||||
import type { Language } from 'playwright-core/src/utils/isomorphic/locatorGenerators';
|
||||
import type { ResourceSnapshot } from '@trace/snapshot';
|
||||
import type * as trace from '@trace/trace';
|
||||
|
||||
|
|
@ -54,26 +54,3 @@ export type PageEntry = {
|
|||
export type ActionEntry = trace.ActionTraceEvent & {
|
||||
log: { time: number, message: string }[];
|
||||
};
|
||||
|
||||
export function createEmptyContext(): ContextEntry {
|
||||
return {
|
||||
origin: 'testRunner',
|
||||
traceUrl: '',
|
||||
startTime: Number.MAX_SAFE_INTEGER,
|
||||
wallTime: Number.MAX_SAFE_INTEGER,
|
||||
endTime: 0,
|
||||
browserName: '',
|
||||
options: {
|
||||
deviceScaleFactor: 1,
|
||||
isMobile: false,
|
||||
viewport: { width: 1280, height: 800 },
|
||||
},
|
||||
pages: [],
|
||||
resources: [],
|
||||
actions: [],
|
||||
events: [],
|
||||
errors: [],
|
||||
stdio: [],
|
||||
hasSource: false,
|
||||
};
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ import type { Language } from '@isomorphic/locatorGenerators';
|
|||
import type { TreeState } from '@web/components/treeView';
|
||||
import { TreeView } from '@web/components/treeView';
|
||||
import type { ActionTraceEventInContext, ActionTreeItem } from './modelUtil';
|
||||
import type { Boundaries } from '../geometry';
|
||||
import type { Boundaries } from './geometry';
|
||||
|
||||
export interface ActionListProps {
|
||||
actions: ActionTraceEventInContext[],
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import * as React from 'react';
|
|||
import './consoleTab.css';
|
||||
import type * as modelUtil from './modelUtil';
|
||||
import { ListView } from '@web/components/listView';
|
||||
import type { Boundaries } from '../geometry';
|
||||
import type { Boundaries } from './geometry';
|
||||
import { clsx, msToString } from '@web/uiUtils';
|
||||
import { ansi2html } from '@web/ansi2html';
|
||||
import { PlaceholderPanel } from './placeholderPanel';
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import type { ContextEntry } from '../entries';
|
||||
import type { ContextEntry } from '../types/entries';
|
||||
import { MultiTraceModel } from './modelUtil';
|
||||
import './embeddedWorkbenchLoader.css';
|
||||
import { Workbench } from './workbench';
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@
|
|||
*/
|
||||
|
||||
import './filmStrip.css';
|
||||
import type { Boundaries, Size } from '../geometry';
|
||||
import type { Boundaries, Size } from './geometry';
|
||||
import * as React from 'react';
|
||||
import { useMeasure, upperBound } from '@web/uiUtils';
|
||||
import type { PageEntry } from '../entries';
|
||||
import type { PageEntry } from '../types/entries';
|
||||
import type { ActionTraceEventInContext, MultiTraceModel } from './modelUtil';
|
||||
import { renderAction } from './actionList';
|
||||
import type { Language } from '@isomorphic/locatorGenerators';
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import type { Language } from '@isomorphic/locatorGenerators';
|
|||
import type { ResourceSnapshot } from '@trace/snapshot';
|
||||
import type * as trace from '@trace/trace';
|
||||
import type { ActionTraceEvent } from '@trace/trace';
|
||||
import type { ContextEntry, PageEntry } from '../entries';
|
||||
import type { ContextEntry, PageEntry } from '../types/entries';
|
||||
import type { StackFrame } from '@protocol/channels';
|
||||
|
||||
const contextSymbol = Symbol('context');
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import type { Entry } from '@trace/har';
|
||||
import * as React from 'react';
|
||||
import type { Boundaries } from '../geometry';
|
||||
import type { Boundaries } from './geometry';
|
||||
import './networkTab.css';
|
||||
import { NetworkResourceDetails } from './networkResourceDetails';
|
||||
import { bytesToString, msToString } from '@web/uiUtils';
|
||||
|
|
@ -24,7 +24,7 @@ import { PlaceholderPanel } from './placeholderPanel';
|
|||
import { context, type MultiTraceModel } from './modelUtil';
|
||||
import { GridView, type RenderedGridCell } from '@web/components/gridView';
|
||||
import { SplitView } from '@web/components/splitView';
|
||||
import type { ContextEntry } from '../entries';
|
||||
import type { ContextEntry } from '../types/entries';
|
||||
import { NetworkFilters, defaultFilterState, type FilterState, type ResourceType } from './networkFilters';
|
||||
|
||||
type NetworkTabModel = {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import type { TabbedPaneTabModel } from '@web/components/tabbedPane';
|
|||
import { TabbedPane } from '@web/components/tabbedPane';
|
||||
import { sha1, useSetting } from '@web/uiUtils';
|
||||
import * as React from 'react';
|
||||
import type { ContextEntry } from '../entries';
|
||||
import type { Boundaries } from '../geometry';
|
||||
import type { ContextEntry } from '../types/entries';
|
||||
import type { Boundaries } from './geometry';
|
||||
import { ActionList } from './actionList';
|
||||
import { ConsoleTab, useConsoleTabModel } from './consoleTab';
|
||||
import { InspectorTab } from './inspectorTab';
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import { clsx, msToString, useMeasure } from '@web/uiUtils';
|
||||
import { GlassPane } from '@web/shared/glassPane';
|
||||
import * as React from 'react';
|
||||
import type { Boundaries } from '../geometry';
|
||||
import type { Boundaries } from './geometry';
|
||||
import { FilmStrip } from './filmStrip';
|
||||
import type { FilmStripPreviewPoint } from './filmStrip';
|
||||
import type { ActionTraceEventInContext, MultiTraceModel } from './modelUtil';
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import '@web/common.css';
|
|||
import '@web/third_party/vscode/codicon.css';
|
||||
import type * as reporterTypes from 'playwright/types/testReporter';
|
||||
import React from 'react';
|
||||
import type { ContextEntry } from '../entries';
|
||||
import type { ContextEntry } from '../types/entries';
|
||||
import type { SourceLocation } from './modelUtil';
|
||||
import { MultiTraceModel } from './modelUtil';
|
||||
import { Workbench } from './workbench';
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ const queryParams = {
|
|||
grepInvert: searchParams.get('grepInvert') || undefined,
|
||||
projects: searchParams.getAll('project'),
|
||||
workers: searchParams.get('workers') || undefined,
|
||||
timeout: searchParams.has('timeout') ? +searchParams.get('timeout')! : undefined,
|
||||
headed: searchParams.has('headed'),
|
||||
updateSnapshots: (searchParams.get('updateSnapshots') as 'all' | 'none' | 'missing' | undefined) || undefined,
|
||||
reporters: searchParams.has('reporter') ? searchParams.getAll('reporter') : undefined,
|
||||
|
|
@ -101,9 +100,9 @@ export const UIModeView: React.FC<{}> = ({
|
|||
const onRevealSource = React.useCallback(() => setRevealSource(true), [setRevealSource]);
|
||||
|
||||
const showTestingOptions = false;
|
||||
const [singleWorker, setSingleWorker] = React.useState(queryParams.workers === '1');
|
||||
const [showBrowser, setShowBrowser] = React.useState(queryParams.headed);
|
||||
const [updateSnapshots, setUpdateSnapshots] = React.useState(queryParams.updateSnapshots === 'all');
|
||||
const [singleWorker, setSingleWorker] = React.useState(false);
|
||||
const [showBrowser, setShowBrowser] = React.useState(false);
|
||||
const [updateSnapshots, setUpdateSnapshots] = React.useState(false);
|
||||
const [darkMode, setDarkMode] = useDarkModeSetting();
|
||||
const [showScreenshot, setShowScreenshot] = useSetting('screenshot-instead-of-snapshot', false);
|
||||
|
||||
|
|
@ -288,12 +287,9 @@ export const UIModeView: React.FC<{}> = ({
|
|||
grepInvert: queryParams.grepInvert,
|
||||
testIds: [...testIds],
|
||||
projects: [...projectFilters].filter(([_, v]) => v).map(([p]) => p),
|
||||
// When started with `--workers=1`, the setting allows to undo that.
|
||||
// Otherwise, fallback to the cli `--workers=X` argument.
|
||||
workers: singleWorker ? '1' : (queryParams.workers === '1' ? undefined : queryParams.workers),
|
||||
timeout: queryParams.timeout,
|
||||
headed: showBrowser,
|
||||
updateSnapshots: updateSnapshots ? 'all' : queryParams.updateSnapshots,
|
||||
...(singleWorker ? { workers: '1' } : {}),
|
||||
...(showBrowser ? { headed: true } : {}),
|
||||
...(updateSnapshots ? { updateSnapshots: 'all' } : {}),
|
||||
reporters: queryParams.reporters,
|
||||
trace: 'on',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import { Timeline } from './timeline';
|
|||
import { MetadataView } from './metadataView';
|
||||
import { AttachmentsTab } from './attachmentsTab';
|
||||
import { AnnotationsTab } from './annotationsTab';
|
||||
import type { Boundaries } from '../geometry';
|
||||
import type { Boundaries } from './geometry';
|
||||
import { InspectorTab } from './inspectorTab';
|
||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||
import { useSetting, msToString, clsx } from '@web/uiUtils';
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||
import * as React from 'react';
|
||||
import type { ContextEntry } from '../entries';
|
||||
import type { ContextEntry } from '../types/entries';
|
||||
import { MultiTraceModel } from './modelUtil';
|
||||
import './workbenchLoader.css';
|
||||
import { toggleTheme } from '@web/theme';
|
||||
|
|
|
|||
|
|
@ -41,11 +41,11 @@ export default defineConfig({
|
|||
emptyOutDir: false,
|
||||
rollupOptions: {
|
||||
input: {
|
||||
sw: path.resolve(__dirname, 'src/sw.ts'),
|
||||
sw: path.resolve(__dirname, 'src/sw-main.ts'),
|
||||
},
|
||||
output: {
|
||||
entryFileNames: info => '[name].bundle.js',
|
||||
assetFileNames: () => '[name].[hash][extname]',
|
||||
entryFileNames: info => 'sw.bundle.js',
|
||||
assetFileNames: () => 'sw.[hash][extname]',
|
||||
manualChunks: undefined,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import type { TestInfo } from '@playwright/test';
|
|||
export type BrowserTestWorkerFixtures = PageWorkerFixtures & {
|
||||
browserVersion: string;
|
||||
defaultSameSiteCookieValue: string;
|
||||
sameSiteStoredValueForNone: string;
|
||||
allowsThirdParty: boolean;
|
||||
browserMajorVersion: number;
|
||||
browserType: BrowserType;
|
||||
|
|
@ -86,6 +87,13 @@ const test = baseTest.extend<BrowserTestTestFixtures, BrowserTestWorkerFixtures>
|
|||
throw new Error('unknown browser - ' + browserName);
|
||||
}, { scope: 'worker' }],
|
||||
|
||||
sameSiteStoredValueForNone: [async ({ browserName, isMac }, run) => {
|
||||
if (browserName === 'webkit' && isMac && parseInt(os.release(), 10) >= 24)
|
||||
await run('Lax');
|
||||
else
|
||||
await run('None');
|
||||
}, { scope: 'worker' }],
|
||||
|
||||
browserMajorVersion: [async ({ browserVersion }, run) => {
|
||||
await run(Number(browserVersion.split('.')[0]));
|
||||
}, { scope: 'worker' }],
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@
|
|||
|
||||
import type { Frame, Page } from 'playwright-core';
|
||||
import { ZipFile } from '../../packages/playwright-core/lib/utils/zipFile';
|
||||
import type { TraceModelBackend } from '../../packages/trace-viewer/src/traceModel';
|
||||
import type { TraceModelBackend } from '../../packages/trace-viewer/src/sw/traceModel';
|
||||
import type { StackFrame } from '../../packages/protocol/src/channels';
|
||||
import { parseClientSideCallMetadata } from '../../packages/playwright-core/lib/utils/isomorphic/traceUtils';
|
||||
import { TraceModel } from '../../packages/trace-viewer/src/traceModel';
|
||||
import { TraceModel } from '../../packages/trace-viewer/src/sw/traceModel';
|
||||
import type { ActionTreeItem } from '../../packages/trace-viewer/src/ui/modelUtil';
|
||||
import { buildActionTree, MultiTraceModel } from '../../packages/trace-viewer/src/ui/modelUtil';
|
||||
import type { ActionTraceEvent, ConsoleMessageTraceEvent, EventTraceEvent, TraceEvent } from '@trace/trace';
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ it('should get multiple cookies', async ({ context, page, server, defaultSameSit
|
|||
]));
|
||||
});
|
||||
|
||||
it('should get cookies from multiple urls', async ({ context, browserName, isWindows }) => {
|
||||
it('should get cookies from multiple urls', async ({ context, browserName, isWindows, sameSiteStoredValueForNone }) => {
|
||||
await context.addCookies([{
|
||||
url: 'https://foo.com',
|
||||
name: 'doggo',
|
||||
|
|
@ -178,7 +178,7 @@ it('should get cookies from multiple urls', async ({ context, browserName, isWin
|
|||
expires: -1,
|
||||
httpOnly: false,
|
||||
secure: true,
|
||||
sameSite: 'None',
|
||||
sameSite: sameSiteStoredValueForNone,
|
||||
}]));
|
||||
});
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ it('should return secure cookies based on HTTP(S) protocol', async ({ context, b
|
|||
}]);
|
||||
});
|
||||
|
||||
it('should add cookies with an expiration', async ({ context }) => {
|
||||
it('should add cookies with an expiration', async ({ context, sameSiteStoredValueForNone }) => {
|
||||
const expires = Math.floor((Date.now() / 1000)) + 3600;
|
||||
await context.addCookies([{
|
||||
url: 'https://foo.com',
|
||||
|
|
@ -293,7 +293,7 @@ it('should add cookies with an expiration', async ({ context }) => {
|
|||
expires,
|
||||
httpOnly: false,
|
||||
secure: true,
|
||||
sameSite: 'None',
|
||||
sameSite: sameSiteStoredValueForNone,
|
||||
}]);
|
||||
{
|
||||
// Rollover to 5-digit year
|
||||
|
|
|
|||
|
|
@ -1245,7 +1245,7 @@ it('should work with connectOverCDP', async ({ browserName, browserType, server
|
|||
}
|
||||
});
|
||||
|
||||
it('should support SameSite cookie attribute over https', async ({ contextFactory, httpsServer, browserName, isWindows }) => {
|
||||
it('should support SameSite cookie attribute over https', async ({ contextFactory, httpsServer, browserName, isWindows, sameSiteStoredValueForNone }) => {
|
||||
// Cookies with SameSite=None must also specify the Secure attribute. WebKit navigation
|
||||
// to HTTP url will fail if the response contains a cookie with Secure attribute, so
|
||||
// we do HTTPS navigation.
|
||||
|
|
@ -1261,6 +1261,8 @@ it('should support SameSite cookie attribute over https', async ({ contextFactor
|
|||
const [cookie] = await page.context().cookies();
|
||||
if (browserName === 'webkit' && isWindows)
|
||||
expect(cookie.sameSite).toBe('None');
|
||||
else if (value === 'None')
|
||||
expect(cookie.sameSite).toBe(sameSiteStoredValueForNone);
|
||||
else
|
||||
expect(cookie.sameSite).toBe(value);
|
||||
});
|
||||
|
|
@ -1290,7 +1292,7 @@ it('fetch should not throw on long set-cookie value', async ({ context, server }
|
|||
expect(cookies.map(c => c.name)).toContain('bar');
|
||||
});
|
||||
|
||||
it('should support set-cookie with SameSite and without Secure attribute over HTTP', async ({ page, server, browserName, isWindows, isLinux }) => {
|
||||
it('should support set-cookie with SameSite and without Secure attribute over HTTP', async ({ page, server, browserName, isWindows, isLinux, sameSiteStoredValueForNone }) => {
|
||||
for (const value of ['None', 'Lax', 'Strict']) {
|
||||
await it.step(`SameSite=${value}`, async () => {
|
||||
server.setRoute('/empty.html', (req, res) => {
|
||||
|
|
@ -1305,6 +1307,8 @@ it('should support set-cookie with SameSite and without Secure attribute over HT
|
|||
expect(cookie).toBeFalsy();
|
||||
else if (browserName === 'webkit' && isWindows)
|
||||
expect(cookie.sameSite).toBe('None');
|
||||
else if (value === 'None')
|
||||
expect(cookie.sameSite).toBe(sameSiteStoredValueForNone);
|
||||
else
|
||||
expect(cookie.sameSite).toBe(value);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -421,18 +421,32 @@ await page.GetByRole(AriaRole.Textbox).PressAsync("Shift+Enter");`);
|
|||
<input name="two"></input>
|
||||
`);
|
||||
|
||||
await page.click('input[name="one"]');
|
||||
await recorder.waitForOutput('JavaScript', 'click');
|
||||
await page.keyboard.type('foobar123');
|
||||
await recorder.waitForOutput('JavaScript', 'foobar123');
|
||||
const input1 = page.locator('input[name="one"]');
|
||||
const input2 = page.locator('input[name="two"]');
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
await recorder.waitForOutput('JavaScript', 'Tab');
|
||||
await page.keyboard.type('barfoo321');
|
||||
// I can't explain it atm, first character is being consumed for no apparent reason.
|
||||
if (browserName === 'webkit' && codegenMode === 'trace-events')
|
||||
await page.waitForTimeout(1000);
|
||||
await recorder.waitForOutput('JavaScript', 'barfoo321');
|
||||
{
|
||||
await input1.click();
|
||||
await recorder.waitForOutput('JavaScript', 'click');
|
||||
await expect(input1).toBeFocused();
|
||||
}
|
||||
|
||||
{
|
||||
await page.keyboard.type('foobar123');
|
||||
await recorder.waitForOutput('JavaScript', 'foobar123');
|
||||
await expect(input1).toHaveValue('foobar123');
|
||||
}
|
||||
|
||||
{
|
||||
await page.keyboard.press('Tab');
|
||||
await recorder.waitForOutput('JavaScript', 'Tab');
|
||||
await expect(input2).toBeFocused();
|
||||
}
|
||||
|
||||
{
|
||||
await page.keyboard.type('barfoo321');
|
||||
await recorder.waitForOutput('JavaScript', 'barfoo321');
|
||||
await expect(input2).toHaveValue('barfoo321');
|
||||
}
|
||||
|
||||
const text = recorder.sources().get('JavaScript')!.text;
|
||||
expect(text).toContain(`
|
||||
|
|
|
|||
|
|
@ -220,55 +220,30 @@ await page.GetByRole(AriaRole.Textbox).SetInputFilesAsync(new[] { });`);
|
|||
page.waitForEvent('download'),
|
||||
page.click('a')
|
||||
]);
|
||||
await Promise.all([
|
||||
page.waitForEvent('download'),
|
||||
page.click('a')
|
||||
]);
|
||||
const sources = await recorder.waitForOutput('JavaScript', 'download1Promise');
|
||||
const sources = await recorder.waitForOutput('JavaScript', 'downloadPromise');
|
||||
|
||||
expect.soft(sources.get('JavaScript')!.text).toContain(`
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await page.getByRole('link', { name: 'Download' }).click();
|
||||
const download = await downloadPromise;`);
|
||||
expect.soft(sources.get('JavaScript')!.text).toContain(`
|
||||
const download1Promise = page.waitForEvent('download');
|
||||
await page.getByRole('link', { name: 'Download' }).click();
|
||||
const download1 = await download1Promise;`);
|
||||
|
||||
expect.soft(sources.get('Java')!.text).toContain(`
|
||||
Download download = page.waitForDownload(() -> {
|
||||
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("Download")).click();
|
||||
});`);
|
||||
expect.soft(sources.get('Java')!.text).toContain(`
|
||||
Download download1 = page.waitForDownload(() -> {
|
||||
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("Download")).click();
|
||||
});`);
|
||||
|
||||
expect.soft(sources.get('Python')!.text).toContain(`
|
||||
with page.expect_download() as download_info:
|
||||
page.get_by_role("link", name="Download").click()
|
||||
download = download_info.value`);
|
||||
expect.soft(sources.get('Python')!.text).toContain(`
|
||||
with page.expect_download() as download1_info:
|
||||
page.get_by_role("link", name="Download").click()
|
||||
download1 = download1_info.value`);
|
||||
|
||||
expect.soft(sources.get('Python Async')!.text).toContain(`
|
||||
async with page.expect_download() as download_info:
|
||||
await page.get_by_role("link", name="Download").click()
|
||||
download = await download_info.value`);
|
||||
expect.soft(sources.get('Python Async')!.text).toContain(`
|
||||
async with page.expect_download() as download1_info:
|
||||
await page.get_by_role("link", name="Download").click()
|
||||
download1 = await download1_info.value`);
|
||||
|
||||
expect.soft(sources.get('C#')!.text).toContain(`
|
||||
var download = await page.RunAndWaitForDownloadAsync(async () =>
|
||||
{
|
||||
await page.GetByRole(AriaRole.Link, new() { Name = "Download" }).ClickAsync();
|
||||
});`);
|
||||
expect.soft(sources.get('C#')!.text).toContain(`
|
||||
var download1 = await page.RunAndWaitForDownloadAsync(async () =>
|
||||
{
|
||||
await page.GetByRole(AriaRole.Link, new() { Name = "Download" }).ClickAsync();
|
||||
});`);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { TestServer } from 'tests/config/testserver';
|
||||
import type { Recorder } from './inspectorTest';
|
||||
import { test, expect } from './inspectorTest';
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
test.describe('cli codegen', () => {
|
||||
test.skip(({ mode }) => mode !== 'default');
|
||||
|
|
@ -90,7 +93,82 @@ await page.GetByRole(AriaRole.Button, new() { Name = "Submit" }).Nth(1).ClickAsy
|
|||
expect(message.text()).toBe('click2');
|
||||
});
|
||||
|
||||
test('should generate frame locators', async ({ openRecorder, server }) => {
|
||||
test('should generate frame locators (1)', async ({ openRecorder, server }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
const { frameHello1 } = await createFrameHierarchy(page, recorder, server);
|
||||
|
||||
const [sources] = await Promise.all([
|
||||
recorder.waitForOutput('JavaScript', 'Hello1'),
|
||||
frameHello1.click('text=Hello1'),
|
||||
]);
|
||||
|
||||
expect.soft(sources.get('JavaScript')!.text).toContain(`
|
||||
await page.locator('#frame1').contentFrame().getByText('Hello1').click();`);
|
||||
|
||||
expect.soft(sources.get('Java')!.text).toContain(`
|
||||
page.locator("#frame1").contentFrame().getByText("Hello1").click();`);
|
||||
|
||||
expect.soft(sources.get('Python')!.text).toContain(`
|
||||
page.locator("#frame1").content_frame.get_by_text("Hello1").click()`);
|
||||
|
||||
expect.soft(sources.get('Python Async')!.text).toContain(`
|
||||
await page.locator("#frame1").content_frame.get_by_text("Hello1").click()`);
|
||||
|
||||
expect.soft(sources.get('C#')!.text).toContain(`
|
||||
await page.Locator("#frame1").ContentFrame.GetByText("Hello1").ClickAsync();`);
|
||||
});
|
||||
|
||||
test('should generate frame locators (2)', async ({ openRecorder, server }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
const { frameHello2 } = await createFrameHierarchy(page, recorder, server);
|
||||
|
||||
const [sources] = await Promise.all([
|
||||
recorder.waitForOutput('JavaScript', 'Hello2'),
|
||||
frameHello2.click('text=Hello2'),
|
||||
]);
|
||||
|
||||
expect.soft(sources.get('JavaScript')!.text).toContain(`
|
||||
await page.locator('#frame1').contentFrame().locator('iframe').contentFrame().getByText('Hello2').click();`);
|
||||
|
||||
expect.soft(sources.get('Java')!.text).toContain(`
|
||||
page.locator("#frame1").contentFrame().locator("iframe").contentFrame().getByText("Hello2").click();`);
|
||||
|
||||
expect.soft(sources.get('Python')!.text).toContain(`
|
||||
page.locator("#frame1").content_frame.locator("iframe").content_frame.get_by_text("Hello2").click()`);
|
||||
|
||||
expect.soft(sources.get('Python Async')!.text).toContain(`
|
||||
await page.locator("#frame1").content_frame.locator("iframe").content_frame.get_by_text("Hello2").click()`);
|
||||
|
||||
expect.soft(sources.get('C#')!.text).toContain(`
|
||||
await page.Locator("#frame1").ContentFrame.Locator("iframe").ContentFrame.GetByText("Hello2").ClickAsync();`);
|
||||
});
|
||||
|
||||
test('should generate frame locators (3)', async ({ openRecorder, server }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
const { frameAnonymous } = await createFrameHierarchy(page, recorder, server);
|
||||
|
||||
const [sources] = await Promise.all([
|
||||
recorder.waitForOutput('JavaScript', 'HelloNameAnonymous'),
|
||||
frameAnonymous.click('text=HelloNameAnonymous'),
|
||||
]);
|
||||
|
||||
expect.soft(sources.get('JavaScript')!.text).toContain(`
|
||||
await page.locator('#frame1').contentFrame().locator('iframe').contentFrame().locator('iframe').nth(2).contentFrame().getByText('HelloNameAnonymous').click();`);
|
||||
|
||||
expect.soft(sources.get('Java')!.text).toContain(`
|
||||
page.locator("#frame1").contentFrame().locator("iframe").contentFrame().locator("iframe").nth(2).contentFrame().getByText("HelloNameAnonymous").click();`);
|
||||
|
||||
expect.soft(sources.get('Python')!.text).toContain(`
|
||||
page.locator("#frame1").content_frame.locator("iframe").content_frame.locator("iframe").nth(2).content_frame.get_by_text("HelloNameAnonymous").click()`);
|
||||
|
||||
expect.soft(sources.get('Python Async')!.text).toContain(`
|
||||
await page.locator("#frame1").content_frame.locator("iframe").content_frame.locator("iframe").nth(2).content_frame.get_by_text("HelloNameAnonymous").click()`);
|
||||
|
||||
expect.soft(sources.get('C#')!.text).toContain(`
|
||||
await page.Locator("#frame1").ContentFrame.Locator("iframe").ContentFrame.Locator("iframe").Nth(2).ContentFrame.GetByText("HelloNameAnonymous").ClickAsync();`);
|
||||
});
|
||||
|
||||
test('should generate frame locators (4)', async ({ openRecorder, server }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
/*
|
||||
iframe
|
||||
|
|
@ -99,8 +177,6 @@ await page.GetByRole(AriaRole.Button, new() { Name = "Submit" }).Nth(1).ClickAsy
|
|||
div Hello2
|
||||
iframe[name=one]
|
||||
div HelloNameOne
|
||||
iframe[name=two]
|
||||
dev HelloNameTwo
|
||||
iframe
|
||||
dev HelloAnonymous
|
||||
*/
|
||||
|
|
@ -109,8 +185,6 @@ await page.GetByRole(AriaRole.Button, new() { Name = "Submit" }).Nth(1).ClickAsy
|
|||
`, server.EMPTY_PAGE, 6);
|
||||
const frameHello1 = page.mainFrame().childFrames()[0];
|
||||
const frameHello2 = frameHello1.childFrames()[0];
|
||||
const frameOne = page.frame({ name: 'one' })!;
|
||||
await frameOne.setContent(`<div>HelloNameOne</div>`);
|
||||
const frameTwo = page.frame({ name: 'two' })!;
|
||||
await frameTwo.setContent(`<div>HelloNameTwo</div>`);
|
||||
const frameAnonymous = frameHello2.childFrames().find(f => !f.name())!;
|
||||
|
|
@ -157,27 +231,6 @@ await page.Locator("#frame1").ContentFrame.GetByText("Hello1").ClickAsync();`);
|
|||
expect.soft(sources.get('C#')!.text).toContain(`
|
||||
await page.Locator("#frame1").ContentFrame.Locator("iframe").ContentFrame.GetByText("Hello2").ClickAsync();`);
|
||||
|
||||
|
||||
[sources] = await Promise.all([
|
||||
recorder.waitForOutput('JavaScript', 'one'),
|
||||
frameOne.click('text=HelloNameOne'),
|
||||
]);
|
||||
|
||||
expect.soft(sources.get('JavaScript')!.text).toContain(`
|
||||
await page.locator('#frame1').contentFrame().locator('iframe').contentFrame().locator('iframe[name="one"]').contentFrame().getByText('HelloNameOne').click();`);
|
||||
|
||||
expect.soft(sources.get('Java')!.text).toContain(`
|
||||
page.locator("#frame1").contentFrame().locator("iframe").contentFrame().locator("iframe[name=\\"one\\"]").contentFrame().getByText("HelloNameOne").click();`);
|
||||
|
||||
expect.soft(sources.get('Python')!.text).toContain(`
|
||||
page.locator("#frame1").content_frame.locator("iframe").content_frame.locator("iframe[name=\\"one\\"]").content_frame.get_by_text("HelloNameOne").click()`);
|
||||
|
||||
expect.soft(sources.get('Python Async')!.text).toContain(`
|
||||
await page.locator("#frame1").content_frame.locator("iframe").content_frame.locator("iframe[name=\\"one\\"]").content_frame.get_by_text("HelloNameOne").click()`);
|
||||
|
||||
expect.soft(sources.get('C#')!.text).toContain(`
|
||||
await page.Locator("#frame1").ContentFrame.Locator("iframe").ContentFrame.Locator("iframe[name=\\"one\\"]").ContentFrame.GetByText("HelloNameOne").ClickAsync();`);
|
||||
|
||||
[sources] = await Promise.all([
|
||||
recorder.waitForOutput('JavaScript', 'HelloNameAnonymous'),
|
||||
frameAnonymous.click('text=HelloNameAnonymous'),
|
||||
|
|
@ -758,3 +811,31 @@ await page.GetByLabel("Coun\\"try").ClickAsync();`);
|
|||
await expect(recorder.page.locator('x-pw-glass')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
async function createFrameHierarchy(page: Page, recorder: Recorder, server: TestServer) {
|
||||
/*
|
||||
iframe
|
||||
div Hello1
|
||||
iframe
|
||||
div Hello2
|
||||
iframe[name=one]
|
||||
div HelloNameOne
|
||||
iframe
|
||||
dev HelloAnonymous
|
||||
*/
|
||||
await recorder.setContentAndWait(`
|
||||
<iframe id=frame1 srcdoc="<div>Hello1</div><iframe srcdoc='<div>Hello2</div><iframe name=one></iframe><iframe name=two></iframe><iframe></iframe>'>">
|
||||
`, server.EMPTY_PAGE, 6);
|
||||
const frameHello1 = page.mainFrame().childFrames()[0];
|
||||
const frameHello2 = frameHello1.childFrames()[0];
|
||||
const frameTwo = page.frame({ name: 'two' })!;
|
||||
await frameTwo.setContent(`<div>HelloNameTwo</div>`);
|
||||
const frameAnonymous = frameHello2.childFrames().find(f => !f.name())!;
|
||||
await frameAnonymous.setContent(`<div>HelloNameAnonymous</div>`);
|
||||
return {
|
||||
frameHello1,
|
||||
frameHello2,
|
||||
frameTwo,
|
||||
frameAnonymous,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export const test = contextTest.extend<CLITestArgs>({
|
|||
},
|
||||
});
|
||||
|
||||
class Recorder {
|
||||
export class Recorder {
|
||||
page: Page;
|
||||
_highlightCallback: Function;
|
||||
_highlightInstalled: boolean;
|
||||
|
|
|
|||
|
|
@ -18,10 +18,17 @@ import { test, expect } from './pageTest';
|
|||
|
||||
test('should work', async ({ page }) => {
|
||||
await page.evaluate(() => {
|
||||
globalThis.objectToDestroy = {};
|
||||
globalThis.objectToDestroy = { hello: 'world' };
|
||||
globalThis.weakRef = new WeakRef(globalThis.objectToDestroy);
|
||||
});
|
||||
|
||||
await page.requestGC();
|
||||
expect(await page.evaluate(() => globalThis.weakRef.deref())).toEqual({ hello: 'world' });
|
||||
|
||||
await page.requestGC();
|
||||
expect(await page.evaluate(() => globalThis.weakRef.deref())).toEqual({ hello: 'world' });
|
||||
|
||||
await page.evaluate(() => globalThis.objectToDestroy = null);
|
||||
await page.forceGarbageCollection();
|
||||
await page.requestGC();
|
||||
expect(await page.evaluate(() => globalThis.weakRef.deref())).toBe(undefined);
|
||||
});
|
||||
|
|
@ -1131,3 +1131,39 @@ test('expect.extend should fall back to legacy behavior', async ({ runInlineTest
|
|||
'bar',
|
||||
]);
|
||||
});
|
||||
|
||||
test('custom asymmetric matchers should work with expect.extend', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'expect-test.spec.ts': `
|
||||
import { test, expect as baseExpect, mergeExpects } from '@playwright/test';
|
||||
const expect1 = baseExpect.extend({
|
||||
isFoo(received: unknown, expected: string) {
|
||||
return { pass: received === 'foo', message: () => '' };
|
||||
},
|
||||
});
|
||||
const expect2 = baseExpect.extend({
|
||||
isSomething(received: unknown, expected: string) {
|
||||
return { pass: received === expected, message: () => '' };
|
||||
},
|
||||
});
|
||||
const expect = mergeExpects(expect1, expect2);
|
||||
test('example', () => {
|
||||
expect('foo').toEqual(expect.isFoo());
|
||||
expect('bar').toEqual(expect.isSomething('bar'));
|
||||
try {
|
||||
expect('foo2').toEqual(expect.isFoo());
|
||||
console.log('should not run 1');
|
||||
} catch (e) {
|
||||
}
|
||||
try {
|
||||
expect('bar2').toEqual(expect.isSomething('bar'));
|
||||
console.log('should not run 2');
|
||||
} catch (e) {
|
||||
}
|
||||
});
|
||||
`,
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.output).not.toContain('should not run');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -117,3 +117,23 @@ test('should not throw type error when using assert', async ({ runInlineTest })
|
|||
expect(result.output).not.toContain(`TypeError: process.stderr.hasColors is not a function`);
|
||||
expect(result.output).toContain(`AssertionError`);
|
||||
});
|
||||
|
||||
test('should have debug colors by default, but respect DEBUG_COLORS=0', async ({ runInlineTest }) => {
|
||||
const files = {
|
||||
'a.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
import debug from 'debug';
|
||||
test('passes', () => {
|
||||
const dbg = debug('example');
|
||||
dbg.color = 34; // red
|
||||
dbg('some text');
|
||||
});
|
||||
`
|
||||
};
|
||||
|
||||
const result1 = await runInlineTest(files, {}, { DEBUG: 'example', DEBUG_COLORS: undefined });
|
||||
expect(result1.rawOutput).toContain('\x1b[38;5;34;1mexample \x1b[0msome text');
|
||||
|
||||
const result2 = await runInlineTest(files, {}, { DEBUG: 'example', DEBUG_COLORS: '0' });
|
||||
expect(result2.rawOutput).not.toContain('\x1b[38;5;34;1mexample \x1b[0msome text');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ class ApiParser {
|
|||
if (!name)
|
||||
throw new Error('Invalid member name ' + spec.text);
|
||||
if (match[1] === 'param') {
|
||||
const arg = this.parseProperty(spec);
|
||||
const arg = this.parseProperty(spec, match[2]);
|
||||
if (!arg)
|
||||
return;
|
||||
arg.name = name;
|
||||
|
|
@ -182,7 +182,7 @@ class ApiParser {
|
|||
}
|
||||
} else {
|
||||
// match[1] === 'option'
|
||||
const p = this.parseProperty(spec);
|
||||
const p = this.parseProperty(spec, match[2]);
|
||||
if (!p)
|
||||
return;
|
||||
let options = method.argsArray.find(o => o.name === 'options');
|
||||
|
|
@ -198,11 +198,14 @@ class ApiParser {
|
|||
|
||||
/**
|
||||
* @param {MarkdownHeaderNode} spec
|
||||
* @param {string} memberName
|
||||
* @returns {docs.Member | null}
|
||||
*/
|
||||
parseProperty(spec) {
|
||||
parseProperty(spec, memberName) {
|
||||
const param = childrenWithoutProperties(spec)[0];
|
||||
const text = /** @type {string}*/(param.text);
|
||||
if (text.substring(text.lastIndexOf('>') + 1).trim())
|
||||
throw new Error(`Extra information after type while processing "${memberName}".\nYou probably need an extra empty line before the description.\n================\n${text}`);
|
||||
let typeStart = text.indexOf('<');
|
||||
while ('?e'.includes(text[typeStart - 1]))
|
||||
typeStart--;
|
||||
|
|
|
|||
|
|
@ -130,14 +130,17 @@ async function run() {
|
|||
{
|
||||
const langs = ['js', 'java', 'python', 'csharp'];
|
||||
const documentationRoot = path.join(PROJECT_DIR, 'docs', 'src');
|
||||
const apiRoot = path.join(documentationRoot, 'api');
|
||||
const testApiRoot = path.join(documentationRoot, 'test-api');
|
||||
const testReporterApiRoot = path.join(documentationRoot, 'test-reporter-api');
|
||||
for (const lang of langs) {
|
||||
try {
|
||||
let documentation = parseApi(path.join(documentationRoot, 'api'));
|
||||
let documentation = parseApi(apiRoot);
|
||||
if (lang === 'js') {
|
||||
documentation = documentation.mergeWith(
|
||||
parseApi(path.join(documentationRoot, 'test-api'), path.join(documentationRoot, 'api', 'params.md'))
|
||||
parseApi(testApiRoot, path.join(documentationRoot, 'api', 'params.md'))
|
||||
).mergeWith(
|
||||
parseApi(path.join(documentationRoot, 'test-reporter-api'))
|
||||
parseApi(testReporterApiRoot)
|
||||
);
|
||||
}
|
||||
documentation.filterForLanguage(lang);
|
||||
|
|
@ -183,7 +186,8 @@ async function run() {
|
|||
// Validates code snippet groups.
|
||||
rootNode = docs.processCodeGroups(rootNode, lang, tabs => tabs.map(tab => tab.spec));
|
||||
// Renders links.
|
||||
documentation.renderLinksInNodes(rootNode);
|
||||
if (!filePath.startsWith(apiRoot) && !filePath.startsWith(testApiRoot) && !filePath.startsWith(testReporterApiRoot))
|
||||
documentation.renderLinksInNodes(rootNode);
|
||||
// Validate links.
|
||||
{
|
||||
md.visitAll(rootNode, node => {
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ const md = require('../markdown');
|
|||
* @typedef {function({
|
||||
* clazz?: Class,
|
||||
* member?: Member,
|
||||
* param?: string,
|
||||
* option?: string,
|
||||
* param?: { name: string, alias: string },
|
||||
* option?: { name: string, alias: string },
|
||||
* href?: string,
|
||||
* }): string|undefined} Renderer
|
||||
*/
|
||||
|
|
@ -737,23 +737,49 @@ function patchLinksInText(classOrMember, text, classesMap, membersMap, linkRende
|
|||
const memberName = p1 + ': ' + p2;
|
||||
const member = membersMap.get(memberName);
|
||||
if (!member)
|
||||
throw new Error('Undefined member references: ' + match);
|
||||
throw new Error(`Undefined member reference: ${match}\n=========\n${text}`);
|
||||
return linkRenderer({ member, href }) || match;
|
||||
}
|
||||
if (p1 === 'param') {
|
||||
let alias = p2;
|
||||
if (classOrMember) {
|
||||
// param/option reference can only be in method or same method parameter comments.
|
||||
const method = /** @type {Member} */(classOrMember).enclosingMethod;
|
||||
const param = method?.argsArray.find(a => a.name === p2);
|
||||
if (!param)
|
||||
throw new Error(`Referenced parameter ${match} not found in the parent method ${method?.name} `);
|
||||
alias = param.alias;
|
||||
if (p1 === 'param' || p1 === 'option') {
|
||||
let /** @type {string } */ name;
|
||||
let /** @type {Member} */ member;
|
||||
if (p2.includes('.')) {
|
||||
// fully-qualified name
|
||||
const [className, memberName, ...rest] = p2.split('.');
|
||||
const maybeMember = membersMap.get(`method: ${className}.${memberName}`);
|
||||
if (!maybeMember)
|
||||
throw new Error(`Undefined reference: ${match}\n=========\n${text}`);
|
||||
member = maybeMember;
|
||||
name = rest.join('.');
|
||||
} else {
|
||||
// non-fully-qualified param/option reference from the same method.
|
||||
if (!classOrMember || !(classOrMember instanceof Member)) {
|
||||
Error.stackTraceLimit = 100;
|
||||
throw new Error(`No parent method to find referenced ${match}\n=========\n${text}`);
|
||||
}
|
||||
const maybeMember = classOrMember.enclosingMethod;
|
||||
if (!maybeMember)
|
||||
throw new Error(`Undefined reference: ${match}\n=========\n${text}`);
|
||||
member = maybeMember;
|
||||
name = p2;
|
||||
}
|
||||
if (p1 === 'param') {
|
||||
const param = member.argsArray.find(a => a.name === name);
|
||||
if (!param)
|
||||
throw new Error(`Referenced parameter ${match} not found in the parent method ${member.name}\n=========\n${text}`);
|
||||
return linkRenderer({ member, param: { name, alias: param.alias }, href }) || match;
|
||||
} else {
|
||||
// p1 === 'option'
|
||||
const options = member.argsArray.find(a => a.name === 'options');
|
||||
const parts = name.split('.');
|
||||
const optionName = parts[0];
|
||||
const option = options?.type?.properties?.find(a => a.name === optionName);
|
||||
if (!option)
|
||||
throw new Error(`Referenced option ${match} not found in the parent method ${member.name}\n=========\n${text}`);
|
||||
parts[0] = option.alias;
|
||||
return linkRenderer({ member, option: { name: optionName, alias: parts.join('.') }, href }) || match;
|
||||
}
|
||||
return linkRenderer({ param: alias, href }) || match;
|
||||
}
|
||||
if (p1 === 'option')
|
||||
return linkRenderer({ option: p2, href }) || match;
|
||||
throw new Error(`Undefined link prefix, expected event|method|property|param|option, got: ` + match);
|
||||
});
|
||||
text = text.replace(/\[([\w]+)\](?:\(([^)]*?)\))?/g, (match, p1, href) => {
|
||||
|
|
@ -775,14 +801,9 @@ function generateSourceCodeComment(spec) {
|
|||
node.liType = 'default';
|
||||
if (node.type === 'code' && node.codeLang)
|
||||
node.codeLang = parseCodeLang(node.codeLang).highlighter;
|
||||
if (node.type === 'note') {
|
||||
// @ts-ignore
|
||||
node.type = 'text';
|
||||
node.text = '**NOTE** ' + node.text;
|
||||
}
|
||||
});
|
||||
// 5 is a typical member doc offset.
|
||||
return md.render(comments, { maxColumns: 120 - 5, omitLastCR: true, flattenText: true });
|
||||
return md.render(comments, { maxColumns: 120 - 5, omitLastCR: true, flattenText: true, noteMode: 'compact' });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -830,7 +851,8 @@ function patchCSharpOptionOverloads(optionsArg, options = {}) {
|
|||
}
|
||||
if (options.csharpOptionOverloadsShortNotation) {
|
||||
const newProp = prop.clone();
|
||||
newProp.alias = newProp.name = shortNotation.join('|');
|
||||
newProp.name = prop.name;
|
||||
newProp.alias = shortNotation.join('|');
|
||||
propsToAdd.push(newProp);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue