Compare commits

...

14 commits

Author SHA1 Message Date
Pavel Feldman b9e9f4dfaa cherry-pick(#14428): fix(ts): export types explicitly 2022-05-26 15:52:18 -07:00
Ross Wollman bb1da5ec1e fix: expect.toHaveScreenshot.animations types (#14387)
This makes the docs/types match the code which has already been
released.

Relevant code to traverse up from:
- 3e084829c0/packages/playwright-core/src/server/screenshotter.ts (L89)
- 3e084829c0/packages/playwright-core/src/server/screenshotter.ts (L118)

Fixes #14385
2022-05-24 11:36:03 -07:00
Andrey Lushnikov 5965143b73 docs: fix docs for toHaveScreenshot (#14280) 2022-05-24 11:35:55 -07:00
Pavel Feldman 38fe4a5102 chery-pick: fix(ct): do not break if there are no components (2) 2022-05-23 14:40:02 -07:00
Pavel Feldman 95614581db chery-pick(#14362): fix(ct): do not break if there are no components 2022-05-23 14:39:34 -07:00
Andrey Lushnikov 41c6aaf426
chore: mark v1.22.2 (#14289) 2022-05-20 19:26:06 -07:00
Ross Wollman abd4258080
chery-pick(#14267): fix: page.locator.focus() and page.locator(…).type(…) (#14296)
cherry-pick of fbb364c1cd0dec9e4a75b885d51e67ca397b1d6a:

Fixes focus and blur management when `page.locator(…).focus()`  and  `page.locator(…).type(…)` are used which was regressed by 7a5b070 (#13510).

However, some elements are [not focusable](https://html.spec.whatwg.org/multipage/interaction.html#focusable-area), so we were blurring incorrectly, and losing focus that we should have maintained.

Two regression tests were added that pass on the commit prior to 7a5b070e95 (and match manual testing/expectations):

* `page.locator(…).focus()`: _keeps focus on element when attempting to focus a non-focusable element_
* `page.locator(…).type(…)`: _should type repeatedly in input in shadow dom_

Additionally, a third test (_should type repeatedly in input in shadow dom_) was added to check the invariant from #13510 that states:

> This affects [contenteditable] elements, but not input elements.

and allows us to introduce the targeted fix (contenteditble check before blur) without breaking FF again.

And _should type repeatedly in contenteditable in shadow dom with nested elements_ was added to ensure the above fix works with nest contenteditble detection.

Fixes #14254.
2022-05-19 15:19:39 -07:00
Andrey Lushnikov 14782d7a6b
cherry-pick(#14264): chore: more testing-friendly trace (#14276)
SHA 432c52d31a

Co-authored-by: Pavel Feldman <pavel.feldman@gmail.com>
2022-05-19 07:04:32 -07:00
Andrey Lushnikov baa6aa9f63
cherry-pick(#14243): fix(ct): recreate context on option change (#14257)
Co-authored-by: Pavel Feldman <pavel.feldman@gmail.com>
2022-05-19 06:10:37 -07:00
Pavel Feldman de23a1b3d2 cherry-pick(#14230): fix: include @types/node in pwt deps 2022-05-17 20:39:23 -07:00
Andrey Lushnikov 28d4804a70
chore: mark 1.22.1 (#14210) 2022-05-17 03:00:26 -07:00
Max Schmitt cce4550cb1
cherry-pick(#14200): chore: fix wrong toHaveScreenshot defaults in docs (#14201) 2022-05-17 07:42:20 +03:00
Andrey Lushnikov 263ae4028a
cherry-pick(#14174): fix(types): fix the toHaveScreenshot types (#14194)
Co-authored-by: Andrey Lushnikov <aslushnikov@gmail.com>
Co-authored-by: Pavel Feldman <pavel.feldman@gmail.com>
2022-05-16 08:32:38 -07:00
Andrey Lushnikov 9177bd1614
chore: mark v1.22.0 (#14131) 2022-05-12 11:13:57 -07:00
29 changed files with 845 additions and 209 deletions

View file

@ -1002,38 +1002,54 @@ Property value.
### option: LocatorAssertions.toHaveJSProperty.timeout = %%-csharp-java-python-assertions-timeout-%%
## async method: LocatorAssertions.toHaveScreenshot
## async method: LocatorAssertions.toHaveScreenshot#1
* langs: js
Ensures that [Locator] resolves to a given screenshot. This function will re-take
screenshots until it matches with the saved expectation.
This function will wait until two consecutive locator screenshots
yield the same result, and then compare the last screenshot with the expectation.
If there's no expectation yet, it will wait until two consecutive screenshots
yield the same result, and save the last one as an expectation.
```js
const locator = page.locator('button');
await expect(locator).toHaveScreenshot('image.png');
```
### param: LocatorAssertions.toHaveScreenshot#1.name
- `name` <[string]|[Array]<[string]>>
Snapshot name.
### option: LocatorAssertions.toHaveScreenshot#1.timeout = %%-js-assertions-timeout-%%
### option: LocatorAssertions.toHaveScreenshot#1.animations = %%-screenshot-option-animations-default-disabled-%%
### option: LocatorAssertions.toHaveScreenshot#1.caret = %%-screenshot-option-caret-%%
### option: LocatorAssertions.toHaveScreenshot#1.mask = %%-screenshot-option-mask-%%
### option: LocatorAssertions.toHaveScreenshot#1.omitBackground = %%-screenshot-option-omit-background-%%
### option: LocatorAssertions.toHaveScreenshot#1.scale = %%-screenshot-option-scale-default-css-%%
### option: LocatorAssertions.toHaveScreenshot#1.maxDiffPixels = %%-assertions-max-diff-pixels-%%
### option: LocatorAssertions.toHaveScreenshot#1.maxDiffPixelRatio = %%-assertions-max-diff-pixel-ratio-%%
### option: LocatorAssertions.toHaveScreenshot#1.threshold = %%-assertions-threshold-%%
## async method: LocatorAssertions.toHaveScreenshot#2
* langs: js
This function will wait until two consecutive locator screenshots
yield the same result, and then compare the last screenshot with the expectation.
```js
const locator = page.locator('button');
await expect(locator).toHaveScreenshot();
```
### option: LocatorAssertions.toHaveScreenshot.timeout = %%-js-assertions-timeout-%%
### option: LocatorAssertions.toHaveScreenshot.timeout = %%-csharp-java-python-assertions-timeout-%%
### option: LocatorAssertions.toHaveScreenshot#2.timeout = %%-js-assertions-timeout-%%
### option: LocatorAssertions.toHaveScreenshot#2.animations = %%-screenshot-option-animations-default-disabled-%%
### option: LocatorAssertions.toHaveScreenshot.animations = %%-screenshot-option-animations-%%
### option: LocatorAssertions.toHaveScreenshot.caret = %%-screenshot-option-caret-%%
### option: LocatorAssertions.toHaveScreenshot.mask = %%-screenshot-option-mask-%%
### option: LocatorAssertions.toHaveScreenshot.omitBackground = %%-screenshot-option-omit-background-%%
### option: LocatorAssertions.toHaveScreenshot.scale = %%-screenshot-option-scale-%%
### option: LocatorAssertions.toHaveScreenshot.maxDiffPixels = %%-assertions-max-diff-pixels-%%
### option: LocatorAssertions.toHaveScreenshot.maxDiffPixelRatio = %%-assertions-max-diff-pixel-ratio-%%
### option: LocatorAssertions.toHaveScreenshot.threshold = %%-assertions-threshold-%%
### option: LocatorAssertions.toHaveScreenshot#2.caret = %%-screenshot-option-caret-%%
### option: LocatorAssertions.toHaveScreenshot#2.mask = %%-screenshot-option-mask-%%
### option: LocatorAssertions.toHaveScreenshot#2.omitBackground = %%-screenshot-option-omit-background-%%
### option: LocatorAssertions.toHaveScreenshot#2.scale = %%-screenshot-option-scale-default-css-%%
### option: LocatorAssertions.toHaveScreenshot#2.maxDiffPixels = %%-assertions-max-diff-pixels-%%
### option: LocatorAssertions.toHaveScreenshot#2.maxDiffPixelRatio = %%-assertions-max-diff-pixel-ratio-%%
### option: LocatorAssertions.toHaveScreenshot#2.threshold = %%-assertions-threshold-%%
## async method: LocatorAssertions.toHaveText

View file

@ -114,41 +114,55 @@ Expected substring or RegExp.
### option: PageAssertions.NotToHaveURL.timeout = %%-csharp-java-python-assertions-timeout-%%
## async method: PageAssertions.toHaveScreenshot
## async method: PageAssertions.toHaveScreenshot#1
* langs: js
Ensures that the page resolves to a given screenshot. This function will re-take
screenshots until it matches with the saved expectation.
This function will wait until two consecutive page screenshots
yield the same result, and then compare the last screenshot with the expectation.
If there's no expectation yet, it will wait until two consecutive screenshots
yield the same result, and save the last one as an expectation.
```js
await expect(page).toHaveScreenshot('image.png');
```
### param: PageAssertions.toHaveScreenshot#1.name
- `name` <[string]|[Array]<[string]>>
Snapshot name.
### option: PageAssertions.toHaveScreenshot#1.timeout = %%-js-assertions-timeout-%%
### option: PageAssertions.toHaveScreenshot#1.animations = %%-screenshot-option-animations-default-disabled-%%
### option: PageAssertions.toHaveScreenshot#1.caret = %%-screenshot-option-caret-%%
### option: PageAssertions.toHaveScreenshot#1.clip = %%-screenshot-option-clip-%%
### option: PageAssertions.toHaveScreenshot#1.fullPage = %%-screenshot-option-full-page-%%
### option: PageAssertions.toHaveScreenshot#1.mask = %%-screenshot-option-mask-%%
### option: PageAssertions.toHaveScreenshot#1.omitBackground = %%-screenshot-option-omit-background-%%
### option: PageAssertions.toHaveScreenshot#1.scale = %%-screenshot-option-scale-default-css-%%
### option: PageAssertions.toHaveScreenshot#1.maxDiffPixels = %%-assertions-max-diff-pixels-%%
### option: PageAssertions.toHaveScreenshot#1.maxDiffPixelRatio = %%-assertions-max-diff-pixel-ratio-%%
### option: PageAssertions.toHaveScreenshot#1.threshold = %%-assertions-threshold-%%
## async method: PageAssertions.toHaveScreenshot#2
* langs: js
This function will wait until two consecutive page screenshots
yield the same result, and then compare the last screenshot with the expectation.
```js
await expect(page).toHaveScreenshot();
```
### option: PageAssertions.toHaveScreenshot.timeout = %%-js-assertions-timeout-%%
### option: PageAssertions.toHaveScreenshot.timeout = %%-csharp-java-python-assertions-timeout-%%
### option: PageAssertions.toHaveScreenshot.animations = %%-screenshot-option-animations-%%
### option: PageAssertions.toHaveScreenshot.caret = %%-screenshot-option-caret-%%
### option: PageAssertions.toHaveScreenshot.clip = %%-screenshot-option-clip-%%
### option: PageAssertions.toHaveScreenshot.fullPage = %%-screenshot-option-full-page-%%
### option: PageAssertions.toHaveScreenshot.mask = %%-screenshot-option-mask-%%
### option: PageAssertions.toHaveScreenshot.omitBackground = %%-screenshot-option-omit-background-%%
### option: PageAssertions.toHaveScreenshot.scale = %%-screenshot-option-scale-%%
### option: PageAssertions.toHaveScreenshot.maxDiffPixels = %%-assertions-max-diff-pixels-%%
### option: PageAssertions.toHaveScreenshot.maxDiffPixelRatio = %%-assertions-max-diff-pixel-ratio-%%
### option: PageAssertions.toHaveScreenshot.threshold = %%-assertions-threshold-%%
### option: PageAssertions.toHaveScreenshot#2.timeout = %%-js-assertions-timeout-%%
### option: PageAssertions.toHaveScreenshot#2.animations = %%-screenshot-option-animations-default-disabled-%%
### option: PageAssertions.toHaveScreenshot#2.caret = %%-screenshot-option-caret-%%
### option: PageAssertions.toHaveScreenshot#2.clip = %%-screenshot-option-clip-%%
### option: PageAssertions.toHaveScreenshot#2.fullPage = %%-screenshot-option-full-page-%%
### option: PageAssertions.toHaveScreenshot#2.mask = %%-screenshot-option-mask-%%
### option: PageAssertions.toHaveScreenshot#2.omitBackground = %%-screenshot-option-omit-background-%%
### option: PageAssertions.toHaveScreenshot#2.scale = %%-screenshot-option-scale-default-css-%%
### option: PageAssertions.toHaveScreenshot#2.maxDiffPixels = %%-assertions-max-diff-pixels-%%
### option: PageAssertions.toHaveScreenshot#2.maxDiffPixelRatio = %%-assertions-max-diff-pixel-ratio-%%
### option: PageAssertions.toHaveScreenshot#2.threshold = %%-assertions-threshold-%%
## async method: PageAssertions.toHaveTitle

View file

@ -910,6 +910,15 @@ When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animatio
Defaults to `"allow"` that leaves animations untouched.
## screenshot-option-animations-default-disabled
- `animations` <[ScreenshotAnimations]<"disabled"|"allow">>
When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment depending on their duration:
* finite animations are fast-forwarded to completion, so they'll fire `transitionend` event.
* infinite animations are canceled to initial state, and then played over after the screenshot.
Defaults to `"disabled"` that disables animations.
## screenshot-option-omit-background
- `omitBackground` <[boolean]>
@ -957,7 +966,16 @@ An object which specifies clipping of the resulting image. Should have the follo
## screenshot-option-scale
- `scale` <[ScreenshotScale]<"css"|"device">>
When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of high-dpi devices will be twice as large or even larger. Defaults to `"device"`.
When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of high-dpi devices will be twice as large or even larger.
Defaults to `"device"`.
## screenshot-option-scale-default-css
- `scale` <[ScreenshotScale]<"css"|"device">>
When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of high-dpi devices will be twice as large or even larger.
Defaults to `"css"`.
## screenshot-option-caret
- `caret` <[ScreenshotCaret]<"hide"|"initial">>

View file

@ -143,7 +143,7 @@ Alternatively, you can use [Command line tools](./cli.md#install-system-dependen
pool:
vmImage: 'ubuntu-20.04'
container: mcr.microsoft.com/playwright:v1.22.0-focal
container: mcr.microsoft.com/playwright:v1.22.2-focal
steps:
...
@ -157,7 +157,7 @@ Running Playwright on CircleCI requires the following steps:
```yml
docker:
- image: mcr.microsoft.com/playwright:v1.22.0-focal
- image: mcr.microsoft.com/playwright:v1.22.2-focal
environment:
NODE_ENV: development # Needed if playwright is in `devDependencies`
```
@ -179,7 +179,7 @@ to run tests on Jenkins.
```groovy
pipeline {
agent { docker { image 'mcr.microsoft.com/playwright:v1.22.0-focal' } }
agent { docker { image 'mcr.microsoft.com/playwright:v1.22.2-focal' } }
stages {
stage('e2e-tests') {
steps {
@ -196,7 +196,7 @@ pipeline {
Bitbucket Pipelines can use public [Docker images as build environments](https://confluence.atlassian.com/bitbucket/use-docker-images-as-build-environments-792298897.html). To run Playwright tests on Bitbucket, use our public Docker image ([see Dockerfile](./docker.md)).
```yml
image: mcr.microsoft.com/playwright:v1.22.0-focal
image: mcr.microsoft.com/playwright:v1.22.2-focal
```
### GitLab CI
@ -209,7 +209,7 @@ stages:
tests:
stage: test
image: mcr.microsoft.com/playwright:v1.22.0-focal
image: mcr.microsoft.com/playwright:v1.22.2-focal
script:
...
```

View file

@ -14,19 +14,19 @@ This image is published on [Docker Hub].
### Pull the image
```bash js
docker pull mcr.microsoft.com/playwright:v1.22.0-focal
docker pull mcr.microsoft.com/playwright:v1.22.2-focal
```
```bash python
docker pull mcr.microsoft.com/playwright/python:v1.22.0-focal
docker pull mcr.microsoft.com/playwright/python:v1.22.2-focal
```
```bash csharp
docker pull mcr.microsoft.com/playwright/dotnet:v1.22.0-focal
docker pull mcr.microsoft.com/playwright/dotnet:v1.22.2-focal
```
```bash java
docker pull mcr.microsoft.com/playwright/java:v1.22.0-focal
docker pull mcr.microsoft.com/playwright/java:v1.22.2-focal
```
### Run the image
@ -38,19 +38,19 @@ By default, the Docker image will use the `root` user to run the browsers. This
On trusted websites, you can avoid creating a separate user and use root for it since you trust the code which will run on the browsers.
```bash js
docker run -it --rm --ipc=host mcr.microsoft.com/playwright:v1.22.0-focal /bin/bash
docker run -it --rm --ipc=host mcr.microsoft.com/playwright:v1.22.2-focal /bin/bash
```
```bash python
docker run -it --rm --ipc=host mcr.microsoft.com/playwright/python:v1.22.0-focal /bin/bash
docker run -it --rm --ipc=host mcr.microsoft.com/playwright/python:v1.22.2-focal /bin/bash
```
```bash csharp
docker run -it --rm --ipc=host mcr.microsoft.com/playwright/dotnet:v1.22.0-focal /bin/bash
docker run -it --rm --ipc=host mcr.microsoft.com/playwright/dotnet:v1.22.2-focal /bin/bash
```
```bash java
docker run -it --rm --ipc=host mcr.microsoft.com/playwright/java:v1.22.0-focal /bin/bash
docker run -it --rm --ipc=host mcr.microsoft.com/playwright/java:v1.22.2-focal /bin/bash
```
#### Crawling and scraping
@ -58,19 +58,19 @@ docker run -it --rm --ipc=host mcr.microsoft.com/playwright/java:v1.22.0-focal /
On untrusted websites, it's recommended to use a separate user for launching the browsers in combination with the seccomp profile. Inside the container or if you are using the Docker image as a base image you have to use `adduser` for it.
```bash js
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright:v1.22.0-focal /bin/bash
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright:v1.22.2-focal /bin/bash
```
```bash python
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/python:v1.22.0-focal /bin/bash
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/python:v1.22.2-focal /bin/bash
```
```bash csharp
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/dotnet:v1.22.0-focal /bin/bash
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/dotnet:v1.22.2-focal /bin/bash
```
```bash java
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/java:v1.22.0-focal /bin/bash
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/java:v1.22.2-focal /bin/bash
```
[`seccomp_profile.json`](https://github.com/microsoft/playwright/blob/main/utils/docker/seccomp_profile.json) is needed to run Chromium with sandbox. This is a [default Docker seccomp profile](https://github.com/docker/engine/blob/d0d99b04cf6e00ed3fc27e81fc3d94e7eda70af3/profiles/seccomp/default.json) with extra user namespace cloning permissions:

View file

@ -59,7 +59,7 @@ title: "Release notes"
await submitButton.click();
```
- New web-first assertions [`method: PageAssertions.toHaveScreenshot`] and [`method: LocatorAssertions.toHaveScreenshot`] that
- New web-first assertions [`method: PageAssertions.toHaveScreenshot#1`] and [`method: LocatorAssertions.toHaveScreenshot#1`] that
wait for screenshot stabilization and enhances test reliability.
The new assertions has screenshot-specific defaults, such as:
@ -71,7 +71,7 @@ title: "Release notes"
await expect(page).toHaveScreenshot();
```
The new [`method: PageAssertions.toHaveScreenshot`] saves screenshots at the same
The new [`method: PageAssertions.toHaveScreenshot#1`] saves screenshots at the same
location as [`method: ScreenshotAssertions.toMatchSnapshot#1`].

View file

@ -36,11 +36,11 @@ export default config;
## property: TestConfig.expect
- type: ?<[Object]>
- `timeout` ?<[int]> Default timeout for async expect matchers in milliseconds, defaults to 5000ms.
- `toHaveScreenshot` ?<[Object]> Configuration for the [`method: PageAssertions.toHaveScreenshot`] method.
- `toHaveScreenshot` ?<[Object]> Configuration for the [`method: PageAssertions.toHaveScreenshot#1`] method.
- `threshold` ?<[float]> an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same pixel in compared images, between zero (strict) and one (lax). Defaults 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"|"disable">> See [`option: animations`] in [`method: Page.screenshot`]. Defaults to `"disable"`.
- `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"`.
- `toMatchSnapshot` ?<[Object]> Configuration for the [`method: ScreenshotAssertions.toMatchSnapshot#1`] method.
@ -419,7 +419,7 @@ export default config;
* experimental
- type: ?<[string]>
The base directory, relative to the config file, for screenshot files created with [`method: PageAssertions.toHaveScreenshot`]. Defaults to
The base directory, relative to the config file, for screenshot files created with [`method: PageAssertions.toHaveScreenshot#1`]. Defaults to
```
<directory-of-configuration-file>/__screenshots__/<platform name>/<project name>

View file

@ -107,11 +107,11 @@ export default config;
## property: TestProject.expect
- type: ?<[Object]>
- `timeout` ?<[int]> Default timeout for async expect matchers in milliseconds, defaults to 5000ms.
- `toHaveScreenshot` ?<[Object]> Configuration for the [`method: PageAssertions.toHaveScreenshot`] method.
- `toHaveScreenshot` ?<[Object]> Configuration for the [`method: PageAssertions.toHaveScreenshot#1`] method.
- `threshold` ?<[float]> an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same pixel in compared images, between zero (strict) and one (lax). Defaults 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"|"disable">> See [`option: animations`] in [`method: Page.screenshot`]. Defaults to `"disable"`.
- `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"`.
- `toMatchSnapshot` ?<[Object]> Configuration for the [`method: ScreenshotAssertions.toMatchSnapshot#1`] method.

View file

@ -56,7 +56,7 @@ The snapshot name `example-test-1-chromium-darwin.png` consists of a few parts:
If you are not on the same operating system as your CI system, you can use Docker to generate/update the screenshots:
```bash
docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.22.0-focal /bin/bash
docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.22.2-focal /bin/bash
npm install
npx playwright test --update-snapshots
```

60
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "playwright-internal",
"version": "1.22.0-next",
"version": "1.22.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "playwright-internal",
"version": "1.22.0-next",
"version": "1.22.2",
"license": "Apache-2.0",
"workspaces": [
"packages/*"
@ -981,8 +981,7 @@
"node_modules/@types/node": {
"version": "14.17.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.15.tgz",
"integrity": "sha512-D1sdW0EcSCmNdLKBGMYb38YsHUS6JcM7yQ6sLQ9KuZ35ck7LYCKE7kYFHOO59ayFOY3zobWVZxf4KXhYHcHYFA==",
"dev": true
"integrity": "sha512-D1sdW0EcSCmNdLKBGMYb38YsHUS6JcM7yQ6sLQ9KuZ35ck7LYCKE7kYFHOO59ayFOY3zobWVZxf4KXhYHcHYFA=="
},
"node_modules/@types/normalize-package-data": {
"version": "2.4.1",
@ -5668,11 +5667,11 @@
"version": "0.0.0"
},
"packages/playwright": {
"version": "1.22.0-next",
"version": "1.22.2",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
},
"bin": {
"playwright": "cli.js"
@ -5682,11 +5681,11 @@
}
},
"packages/playwright-chromium": {
"version": "1.22.0-next",
"version": "1.22.2",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
},
"bin": {
"playwright": "cli.js"
@ -5696,7 +5695,7 @@
}
},
"packages/playwright-core": {
"version": "1.22.0-next",
"version": "1.22.2",
"license": "Apache-2.0",
"bin": {
"playwright": "cli.js"
@ -5707,10 +5706,10 @@
},
"packages/playwright-ct-react": {
"name": "@playwright/experimental-ct-react",
"version": "1.22.0-next",
"version": "1.22.2",
"license": "Apache-2.0",
"dependencies": {
"@playwright/test": "1.22.0-next",
"@playwright/test": "1.22.2",
"@vitejs/plugin-react": "^1.0.7",
"vite": "^2.9.5"
},
@ -5720,10 +5719,10 @@
},
"packages/playwright-ct-svelte": {
"name": "@playwright/experimental-ct-svelte",
"version": "1.22.0-next",
"version": "1.22.2",
"license": "Apache-2.0",
"dependencies": {
"@playwright/test": "1.22.0-next",
"@playwright/test": "1.22.2",
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
"vite": "^2.9.5"
},
@ -5733,10 +5732,10 @@
},
"packages/playwright-ct-vue": {
"name": "@playwright/experimental-ct-vue",
"version": "1.22.0-next",
"version": "1.22.2",
"license": "Apache-2.0",
"dependencies": {
"@playwright/test": "1.22.0-next",
"@playwright/test": "1.22.2",
"@vitejs/plugin-vue": "^2.3.1",
"vite": "^2.9.5"
},
@ -5745,11 +5744,11 @@
}
},
"packages/playwright-firefox": {
"version": "1.22.0-next",
"version": "1.22.2",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
},
"bin": {
"playwright": "cli.js"
@ -5760,10 +5759,10 @@
},
"packages/playwright-test": {
"name": "@playwright/test",
"version": "1.22.0-next",
"version": "1.22.2",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
},
"bin": {
"playwright": "cli.js"
@ -5773,11 +5772,11 @@
}
},
"packages/playwright-webkit": {
"version": "1.22.0-next",
"version": "1.22.2",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
},
"bin": {
"playwright": "cli.js"
@ -6385,7 +6384,7 @@
"@playwright/experimental-ct-react": {
"version": "file:packages/playwright-ct-react",
"requires": {
"@playwright/test": "1.22.0-next",
"@playwright/test": "1.22.2",
"@vitejs/plugin-react": "^1.0.7",
"vite": "^2.9.5"
}
@ -6393,7 +6392,7 @@
"@playwright/experimental-ct-svelte": {
"version": "file:packages/playwright-ct-svelte",
"requires": {
"@playwright/test": "1.22.0-next",
"@playwright/test": "1.22.2",
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
"vite": "^2.9.5"
}
@ -6401,7 +6400,7 @@
"@playwright/experimental-ct-vue": {
"version": "file:packages/playwright-ct-vue",
"requires": {
"@playwright/test": "1.22.0-next",
"@playwright/test": "1.22.2",
"@vitejs/plugin-vue": "^2.3.1",
"vite": "^2.9.5"
}
@ -6409,7 +6408,7 @@
"@playwright/test": {
"version": "file:packages/playwright-test",
"requires": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
}
},
"@rollup/pluginutils": {
@ -6466,8 +6465,7 @@
"@types/node": {
"version": "14.17.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.15.tgz",
"integrity": "sha512-D1sdW0EcSCmNdLKBGMYb38YsHUS6JcM7yQ6sLQ9KuZ35ck7LYCKE7kYFHOO59ayFOY3zobWVZxf4KXhYHcHYFA==",
"dev": true
"integrity": "sha512-D1sdW0EcSCmNdLKBGMYb38YsHUS6JcM7yQ6sLQ9KuZ35ck7LYCKE7kYFHOO59ayFOY3zobWVZxf4KXhYHcHYFA=="
},
"@types/normalize-package-data": {
"version": "2.4.1",
@ -8968,13 +8966,13 @@
"playwright": {
"version": "file:packages/playwright",
"requires": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
}
},
"playwright-chromium": {
"version": "file:packages/playwright-chromium",
"requires": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
}
},
"playwright-core": {
@ -8983,13 +8981,13 @@
"playwright-firefox": {
"version": "file:packages/playwright-firefox",
"requires": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
}
},
"playwright-webkit": {
"version": "file:packages/playwright-webkit",
"requires": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
}
},
"postcss": {

View file

@ -1,7 +1,7 @@
{
"name": "playwright-internal",
"private": true,
"version": "1.22.0-next",
"version": "1.22.2",
"description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",

View file

@ -1,6 +1,6 @@
{
"name": "playwright-chromium",
"version": "1.22.0-next",
"version": "1.22.2",
"description": "A high-level API to automate Chromium",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -13,6 +13,7 @@
"license": "Apache-2.0",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.mjs",
"require": "./index.js"
},
@ -26,6 +27,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "playwright-core",
"version": "1.22.0-next",
"version": "1.22.2",
"description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -13,6 +13,7 @@
"license": "Apache-2.0",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.mjs",
"require": "./index.js"
},

View file

@ -674,7 +674,7 @@ export class InjectedScript {
const activeElement = (node.getRootNode() as (Document | ShadowRoot)).activeElement;
const wasFocused = activeElement === node && node.ownerDocument && node.ownerDocument.hasFocus();
if (!wasFocused && activeElement && (activeElement as HTMLElement | SVGElement).blur) {
if ((node as HTMLElement).isContentEditable && !wasFocused && activeElement && (activeElement as HTMLElement | SVGElement).blur) {
// Workaround the Firefox bug where focusing the element does not switch current
// contenteditable to the new element. However, blurring the previous one helps.
(activeElement as HTMLElement | SVGElement).blur();

View file

@ -8202,7 +8202,9 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to `"device"`.
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"device"`.
*/
scale?: "css"|"device";
@ -15844,7 +15846,9 @@ export interface LocatorScreenshotOptions {
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to `"device"`.
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"device"`.
*/
scale?: "css"|"device";
@ -16027,7 +16031,9 @@ export interface PageScreenshotOptions {
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to `"device"`.
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"device"`.
*/
scale?: "css"|"device";

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-react",
"version": "1.22.0-next",
"version": "1.22.2",
"description": "Playwright Component Testing for React",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -12,12 +12,18 @@
},
"license": "Apache-2.0",
"exports": {
"./register": "./register.mjs",
".": "./index.js"
".": {
"types": "./index.d.ts",
"default": "./index.js"
},
"./register": {
"types": "./register.d.ts",
"default": "./register.mjs"
}
},
"dependencies": {
"@vitejs/plugin-react": "^1.0.7",
"@playwright/test": "1.22.0-next",
"@playwright/test": "1.22.2",
"vite": "^2.9.5"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-svelte",
"version": "1.22.0-next",
"version": "1.22.2",
"description": "Playwright Component Testing for Svelte",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -12,12 +12,18 @@
},
"license": "Apache-2.0",
"exports": {
"./register": "./register.mjs",
".": "./index.js"
".": {
"types": "./index.d.ts",
"default": "./index.js"
},
"./register": {
"types": "./register.d.ts",
"default": "./register.mjs"
}
},
"dependencies": {
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
"@playwright/test": "1.22.0-next",
"@playwright/test": "1.22.2",
"vite": "^2.9.5"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/experimental-ct-vue",
"version": "1.22.0-next",
"version": "1.22.2",
"description": "Playwright Component Testing for Vue",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -12,12 +12,18 @@
},
"license": "Apache-2.0",
"exports": {
"./register": "./register.mjs",
".": "./index.js"
".": {
"types": "./index.d.ts",
"default": "./index.js"
},
"./register": {
"types": "./register.d.ts",
"default": "./register.mjs"
}
},
"dependencies": {
"@vitejs/plugin-vue": "^2.3.1",
"@playwright/test": "1.22.0-next",
"@playwright/test": "1.22.2",
"vite": "^2.9.5"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "playwright-firefox",
"version": "1.22.0-next",
"version": "1.22.2",
"description": "A high-level API to automate Firefox",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -13,6 +13,7 @@
"license": "Apache-2.0",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.mjs",
"require": "./index.js"
},
@ -26,6 +27,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@playwright/test",
"version": "1.22.0-next",
"version": "1.22.2",
"description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -10,6 +10,7 @@
"main": "index.js",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.mjs",
"require": "./index.js"
},
@ -31,6 +32,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.22.0-next"
"@types/node": "*",
"playwright-core": "1.22.2"
}
}

View file

@ -14,35 +14,47 @@
* limitations under the License.
*/
import type { Fixtures, Locator, Page, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs } from './types';
import type { Fixtures, Locator, Page, BrowserContextOptions, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs } from './types';
let boundCallbacksForMount: Function[] = [];
export const fixtures: Fixtures<PlaywrightTestArgs & PlaywrightTestOptions & { mount: (component: any, options: any) => Promise<Locator> }, PlaywrightWorkerArgs & { _workerPage: Page }> = {
_workerPage: [async ({ browser }, use) => {
export const fixtures: Fixtures<PlaywrightTestArgs & PlaywrightTestOptions & { mount: (component: any, options: any) => Promise<Locator> }, PlaywrightWorkerArgs & { _ctPage: { page: Page | undefined, hash: string } }> = {
_ctPage: [{ page: undefined, hash: '' }, { scope: 'worker' }],
context: async ({ page }, use) => {
await use(page.context());
},
page: async ({ _ctPage, browser, viewport, playwright }, use) => {
const defaultContextOptions = (playwright.chromium as any)._defaultContextOptions as BrowserContextOptions;
const hash = contextHash(defaultContextOptions);
if (!_ctPage.page || _ctPage.hash !== hash) {
if (_ctPage.page)
await _ctPage.page.close();
const page = await (browser as any)._wrapApiCall(async () => {
const page = await browser.newPage();
await page.addInitScript('navigator.serviceWorker.register = () => {}');
await page.exposeFunction('__pw_dispatch', (ordinal: number, args: any[]) => {
boundCallbacksForMount[ordinal](...args);
});
await page.goto(process.env.PLAYWRIGHT_VITE_COMPONENTS_BASE_URL!);
return page;
});
}, true);
_ctPage.page = page;
_ctPage.hash = hash;
await use(page);
}, { scope: 'worker' }],
context: async ({ page }, use) => {
await use(page.context());
},
page: async ({ _workerPage, viewport }, use) => {
const page = _workerPage;
await page.goto('about:blank');
} else {
const page = _ctPage.page;
await (page as any)._wrapApiCall(async () => {
await (page as any)._resetForReuse();
await (page.context() as any)._resetForReuse();
await page.goto('about:blank');
await page.setViewportSize(viewport || { width: 1280, height: 800 });
await page.goto(process.env.PLAYWRIGHT_VITE_COMPONENTS_BASE_URL!);
await use(_workerPage);
}, true);
await use(page);
}
},
mount: async ({ page }, use) => {
@ -100,3 +112,28 @@ function wrapFunctions(object: any, page: Page, callbacks: Function[]) {
}
}
}
function contextHash(context: BrowserContextOptions): string {
const hash = {
acceptDownloads: context.acceptDownloads,
bypassCSP: context.bypassCSP,
colorScheme: context.colorScheme,
extraHTTPHeaders: context.extraHTTPHeaders,
forcedColors: context.forcedColors,
geolocation: context.geolocation,
hasTouch: context.hasTouch,
httpCredentials: context.httpCredentials,
ignoreHTTPSErrors: context.ignoreHTTPSErrors,
isMobile: context.isMobile,
javaScriptEnabled: context.javaScriptEnabled,
locale: context.locale,
offline: context.offline,
permissions: context.permissions,
proxy: context.proxy,
storageState: context.storageState,
timezoneId: context.timezoneId,
userAgent: context.userAgent,
deviceScaleFactor: context.deviceScaleFactor,
};
return JSON.stringify(hash);
}

View file

@ -26,7 +26,7 @@ import type { FullConfig } from '../types';
import { assert } from 'playwright-core/lib/utils';
let previewServer: PreviewServer;
const VERSION = 1;
const VERSION = 2;
type CtConfig = {
ctPort?: number;
@ -56,10 +56,12 @@ export function createPlugin(
const outDir = viteConfig?.build?.outDir || (use.ctCacheDir ? path.resolve(rootDir, use.ctCacheDir) : path.resolve(templateDir, '.cache'));
const buildInfoFile = path.join(outDir, 'metainfo.json');
let buildExists = false;
let buildInfo: BuildInfo;
try {
buildInfo = JSON.parse(await fs.promises.readFile(buildInfoFile, 'utf-8')) as BuildInfo;
assert(buildInfo.version === VERSION);
buildExists = true;
} catch (e) {
buildInfo = {
version: VERSION,
@ -75,7 +77,7 @@ export function createPlugin(
// 2. Check if the set of required components has changed.
const hasNewComponents = await checkNewComponents(buildInfo, componentRegistry);
// 3. Check component sources.
const sourcesDirty = hasNewComponents || await checkSources(buildInfo);
const sourcesDirty = !buildExists || hasNewComponents || await checkSources(buildInfo);
viteConfig.root = rootDir;
viteConfig.preview = { port };

View file

@ -486,7 +486,7 @@ interface TestConfig {
/**
* Configuration for the
* [pageAssertions.toHaveScreenshot([options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot)
* [pageAssertions.toHaveScreenshot(name[, options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot-1)
* method.
*/
toHaveScreenshot?: {
@ -508,9 +508,9 @@ interface TestConfig {
/**
* See `animations` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults
* to `"disable"`.
* to `"disabled"`.
*/
animations?: "allow"|"disable";
animations?: "allow"|"disabled";
/**
* See `caret` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults to
@ -3328,27 +3328,25 @@ interface LocatorAssertions {
}): Promise<void>;
/**
* Ensures that [Locator] resolves to a given screenshot. This function will re-take screenshots until it matches with the
* saved expectation.
*
* If there's no expectation yet, it will wait until two consecutive screenshots yield the same result, and save the last
* one as an expectation.
* This function will wait until two consecutive locator screenshots yield the same result, and then compare the last
* screenshot with the expectation.
*
* ```js
* const locator = page.locator('button');
* await expect(locator).toHaveScreenshot();
* await expect(locator).toHaveScreenshot('image.png');
* ```
*
* @param name Snapshot name.
* @param options
*/
toHaveScreenshot(options?: {
toHaveScreenshot(name: string|Array<string>, options?: {
/**
* When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment
* depending on their duration:
* - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event.
* - infinite animations are canceled to initial state, and then played over after the screenshot.
*
* Defaults to `"allow"` that leaves animations untouched.
* Defaults to `"disabled"` that disables animations.
*/
animations?: "disabled"|"allow";
@ -3385,7 +3383,83 @@ interface LocatorAssertions {
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to `"device"`.
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"css"`.
*/
scale?: "css"|"device";
/**
* An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same
* pixel in compared images, between zero (strict) and one (lax), default is configurable with `TestConfig.expect`.
* Defaults to `0.2`.
*/
threshold?: number;
/**
* Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`.
*/
timeout?: number;
}): Promise<void>;
/**
* This function will wait until two consecutive locator screenshots yield the same result, and then compare the last
* screenshot with the expectation.
*
* ```js
* const locator = page.locator('button');
* await expect(locator).toHaveScreenshot();
* ```
*
* @param options
*/
toHaveScreenshot(options?: {
/**
* When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment
* depending on their duration:
* - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event.
* - infinite animations are canceled to initial state, and then played over after the screenshot.
*
* Defaults to `"disabled"` that disables animations.
*/
animations?: "disabled"|"allow";
/**
* When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be changed.
* Defaults to `"hide"`.
*/
caret?: "hide"|"initial";
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* `#FF00FF` that completely covers its bounding box.
*/
mask?: Array<Locator>;
/**
* An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1`. Default is
* configurable with `TestConfig.expect`. Unset by default.
*/
maxDiffPixelRatio?: number;
/**
* An acceptable amount of pixels that could be different, default is configurable with `TestConfig.expect`. Default is
* configurable with `TestConfig.expect`. Unset by default.
*/
maxDiffPixels?: number;
/**
* Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images.
* Defaults to `false`.
*/
omitBackground?: boolean;
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"css"`.
*/
scale?: "css"|"device";
@ -3482,26 +3556,24 @@ interface PageAssertions {
not: PageAssertions;
/**
* Ensures that the page resolves to a given screenshot. This function will re-take screenshots until it matches with the
* saved expectation.
*
* If there's no expectation yet, it will wait until two consecutive screenshots yield the same result, and save the last
* one as an expectation.
* This function will wait until two consecutive page screenshots yield the same result, and then compare the last
* screenshot with the expectation.
*
* ```js
* await expect(page).toHaveScreenshot();
* await expect(page).toHaveScreenshot('image.png');
* ```
*
* @param name Snapshot name.
* @param options
*/
toHaveScreenshot(options?: {
toHaveScreenshot(name: string|Array<string>, options?: {
/**
* When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment
* depending on their duration:
* - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event.
* - infinite animations are canceled to initial state, and then played over after the screenshot.
*
* Defaults to `"allow"` that leaves animations untouched.
* Defaults to `"disabled"` that disables animations.
*/
animations?: "disabled"|"allow";
@ -3569,7 +3641,113 @@ interface PageAssertions {
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to `"device"`.
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"css"`.
*/
scale?: "css"|"device";
/**
* An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same
* pixel in compared images, between zero (strict) and one (lax), default is configurable with `TestConfig.expect`.
* Defaults to `0.2`.
*/
threshold?: number;
/**
* Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`.
*/
timeout?: number;
}): Promise<void>;
/**
* This function will wait until two consecutive page screenshots yield the same result, and then compare the last
* screenshot with the expectation.
*
* ```js
* await expect(page).toHaveScreenshot();
* ```
*
* @param options
*/
toHaveScreenshot(options?: {
/**
* When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment
* depending on their duration:
* - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event.
* - infinite animations are canceled to initial state, and then played over after the screenshot.
*
* Defaults to `"disabled"` that disables animations.
*/
animations?: "disabled"|"allow";
/**
* When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be changed.
* Defaults to `"hide"`.
*/
caret?: "hide"|"initial";
/**
* An object which specifies clipping of the resulting image. Should have the following fields:
*/
clip?: {
/**
* x-coordinate of top-left corner of clip area
*/
x: number;
/**
* y-coordinate of top-left corner of clip area
*/
y: number;
/**
* width of clipping area
*/
width: number;
/**
* height of clipping area
*/
height: number;
};
/**
* When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Defaults to
* `false`.
*/
fullPage?: boolean;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* `#FF00FF` that completely covers its bounding box.
*/
mask?: Array<Locator>;
/**
* An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1`. Default is
* configurable with `TestConfig.expect`. Unset by default.
*/
maxDiffPixelRatio?: number;
/**
* An acceptable amount of pixels that could be different, default is configurable with `TestConfig.expect`. Default is
* configurable with `TestConfig.expect`. Unset by default.
*/
maxDiffPixels?: number;
/**
* Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images.
* Defaults to `false`.
*/
omitBackground?: boolean;
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"css"`.
*/
scale?: "css"|"device";
@ -3820,7 +3998,7 @@ interface TestProject {
/**
* Configuration for the
* [pageAssertions.toHaveScreenshot([options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot)
* [pageAssertions.toHaveScreenshot(name[, options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot-1)
* method.
*/
toHaveScreenshot?: {
@ -3842,9 +4020,9 @@ interface TestProject {
/**
* See `animations` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults
* to `"disable"`.
* to `"disabled"`.
*/
animations?: "allow"|"disable";
animations?: "allow"|"disabled";
/**
* See `caret` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults to

View file

@ -1,6 +1,6 @@
{
"name": "playwright-webkit",
"version": "1.22.0-next",
"version": "1.22.2",
"description": "A high-level API to automate WebKit",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -13,6 +13,7 @@
"license": "Apache-2.0",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.mjs",
"require": "./index.js"
},
@ -26,6 +27,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "playwright",
"version": "1.22.0-next",
"version": "1.22.2",
"description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev",
@ -13,6 +13,7 @@
"license": "Apache-2.0",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.mjs",
"require": "./index.js"
},
@ -26,6 +27,6 @@
"install": "node install.js"
},
"dependencies": {
"playwright-core": "1.22.0-next"
"playwright-core": "1.22.2"
}
}

View file

@ -8204,7 +8204,9 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to `"device"`.
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"device"`.
*/
scale?: "css"|"device";
@ -15846,7 +15848,9 @@ export interface LocatorScreenshotOptions {
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to `"device"`.
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"device"`.
*/
scale?: "css"|"device";
@ -16029,7 +16033,9 @@ export interface PageScreenshotOptions {
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to `"device"`.
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"device"`.
*/
scale?: "css"|"device";
@ -16662,7 +16668,7 @@ interface TestConfig {
/**
* Configuration for the
* [pageAssertions.toHaveScreenshot([options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot)
* [pageAssertions.toHaveScreenshot(name[, options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot-1)
* method.
*/
toHaveScreenshot?: {
@ -16684,9 +16690,9 @@ interface TestConfig {
/**
* See `animations` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults
* to `"disable"`.
* to `"disabled"`.
*/
animations?: "allow"|"disable";
animations?: "allow"|"disabled";
/**
* See `caret` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults to
@ -16966,7 +16972,7 @@ interface TestConfig {
/**
* The base directory, relative to the config file, for screenshot files created with
* [pageAssertions.toHaveScreenshot([options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot).
* [pageAssertions.toHaveScreenshot(name[, options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot-1).
* Defaults to
*
* ```
@ -19534,27 +19540,25 @@ interface LocatorAssertions {
}): Promise<void>;
/**
* Ensures that [Locator] resolves to a given screenshot. This function will re-take screenshots until it matches with the
* saved expectation.
*
* If there's no expectation yet, it will wait until two consecutive screenshots yield the same result, and save the last
* one as an expectation.
* This function will wait until two consecutive locator screenshots yield the same result, and then compare the last
* screenshot with the expectation.
*
* ```js
* const locator = page.locator('button');
* await expect(locator).toHaveScreenshot();
* await expect(locator).toHaveScreenshot('image.png');
* ```
*
* @param name Snapshot name.
* @param options
*/
toHaveScreenshot(options?: {
toHaveScreenshot(name: string|Array<string>, options?: {
/**
* When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment
* depending on their duration:
* - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event.
* - infinite animations are canceled to initial state, and then played over after the screenshot.
*
* Defaults to `"allow"` that leaves animations untouched.
* Defaults to `"disabled"` that disables animations.
*/
animations?: "disabled"|"allow";
@ -19591,7 +19595,83 @@ interface LocatorAssertions {
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to `"device"`.
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"css"`.
*/
scale?: "css"|"device";
/**
* An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same
* pixel in compared images, between zero (strict) and one (lax), default is configurable with `TestConfig.expect`.
* Defaults to `0.2`.
*/
threshold?: number;
/**
* Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`.
*/
timeout?: number;
}): Promise<void>;
/**
* This function will wait until two consecutive locator screenshots yield the same result, and then compare the last
* screenshot with the expectation.
*
* ```js
* const locator = page.locator('button');
* await expect(locator).toHaveScreenshot();
* ```
*
* @param options
*/
toHaveScreenshot(options?: {
/**
* When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment
* depending on their duration:
* - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event.
* - infinite animations are canceled to initial state, and then played over after the screenshot.
*
* Defaults to `"disabled"` that disables animations.
*/
animations?: "disabled"|"allow";
/**
* When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be changed.
* Defaults to `"hide"`.
*/
caret?: "hide"|"initial";
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* `#FF00FF` that completely covers its bounding box.
*/
mask?: Array<Locator>;
/**
* An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1`. Default is
* configurable with `TestConfig.expect`. Unset by default.
*/
maxDiffPixelRatio?: number;
/**
* An acceptable amount of pixels that could be different, default is configurable with `TestConfig.expect`. Default is
* configurable with `TestConfig.expect`. Unset by default.
*/
maxDiffPixels?: number;
/**
* Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images.
* Defaults to `false`.
*/
omitBackground?: boolean;
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"css"`.
*/
scale?: "css"|"device";
@ -19688,26 +19768,24 @@ interface PageAssertions {
not: PageAssertions;
/**
* Ensures that the page resolves to a given screenshot. This function will re-take screenshots until it matches with the
* saved expectation.
*
* If there's no expectation yet, it will wait until two consecutive screenshots yield the same result, and save the last
* one as an expectation.
* This function will wait until two consecutive page screenshots yield the same result, and then compare the last
* screenshot with the expectation.
*
* ```js
* await expect(page).toHaveScreenshot();
* await expect(page).toHaveScreenshot('image.png');
* ```
*
* @param name Snapshot name.
* @param options
*/
toHaveScreenshot(options?: {
toHaveScreenshot(name: string|Array<string>, options?: {
/**
* When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment
* depending on their duration:
* - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event.
* - infinite animations are canceled to initial state, and then played over after the screenshot.
*
* Defaults to `"allow"` that leaves animations untouched.
* Defaults to `"disabled"` that disables animations.
*/
animations?: "disabled"|"allow";
@ -19775,7 +19853,113 @@ interface PageAssertions {
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to `"device"`.
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"css"`.
*/
scale?: "css"|"device";
/**
* An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same
* pixel in compared images, between zero (strict) and one (lax), default is configurable with `TestConfig.expect`.
* Defaults to `0.2`.
*/
threshold?: number;
/**
* Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`.
*/
timeout?: number;
}): Promise<void>;
/**
* This function will wait until two consecutive page screenshots yield the same result, and then compare the last
* screenshot with the expectation.
*
* ```js
* await expect(page).toHaveScreenshot();
* ```
*
* @param options
*/
toHaveScreenshot(options?: {
/**
* When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment
* depending on their duration:
* - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event.
* - infinite animations are canceled to initial state, and then played over after the screenshot.
*
* Defaults to `"disabled"` that disables animations.
*/
animations?: "disabled"|"allow";
/**
* When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be changed.
* Defaults to `"hide"`.
*/
caret?: "hide"|"initial";
/**
* An object which specifies clipping of the resulting image. Should have the following fields:
*/
clip?: {
/**
* x-coordinate of top-left corner of clip area
*/
x: number;
/**
* y-coordinate of top-left corner of clip area
*/
y: number;
/**
* width of clipping area
*/
width: number;
/**
* height of clipping area
*/
height: number;
};
/**
* When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Defaults to
* `false`.
*/
fullPage?: boolean;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* `#FF00FF` that completely covers its bounding box.
*/
mask?: Array<Locator>;
/**
* An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1`. Default is
* configurable with `TestConfig.expect`. Unset by default.
*/
maxDiffPixelRatio?: number;
/**
* An acceptable amount of pixels that could be different, default is configurable with `TestConfig.expect`. Default is
* configurable with `TestConfig.expect`. Unset by default.
*/
maxDiffPixels?: number;
/**
* Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images.
* Defaults to `false`.
*/
omitBackground?: boolean;
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger.
*
* Defaults to `"css"`.
*/
scale?: "css"|"device";
@ -20026,7 +20210,7 @@ interface TestProject {
/**
* Configuration for the
* [pageAssertions.toHaveScreenshot([options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot)
* [pageAssertions.toHaveScreenshot(name[, options])](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot-1)
* method.
*/
toHaveScreenshot?: {
@ -20048,9 +20232,9 @@ interface TestProject {
/**
* See `animations` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults
* to `"disable"`.
* to `"disabled"`.
*/
animations?: "allow"|"disable";
animations?: "allow"|"disabled";
/**
* See `caret` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot). Defaults to

View file

@ -117,3 +117,31 @@ it('clicking checkbox should activate it', async ({ page, browserName, headless,
const nodeName = await page.evaluate(() => document.activeElement.nodeName);
expect(nodeName).toBe('INPUT');
});
it('keeps focus on element when attempting to focus a non-focusable element', async ({ page }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/14254' });
await page.setContent(`
<div id="focusable" tabindex="0">focusable</div>
<div id="non-focusable">not focusable</div>
<script>
window.eventLog = [];
const focusable = document.getElementById("focusable");
focusable.addEventListener('blur', () => window.eventLog.push('blur focusable'));
focusable.addEventListener('focus', () => window.eventLog.push('focus focusable'));
const nonFocusable = document.getElementById("non-focusable");
nonFocusable.addEventListener('blur', () => window.eventLog.push('blur non-focusable'));
nonFocusable.addEventListener('focus', () => window.eventLog.push('focus non-focusable'));
</script>
`);
await page.locator('#focusable').click();
expect.soft(await page.evaluate(() => document.activeElement?.id)).toBe('focusable');
await page.locator('#non-focusable').focus();
expect.soft(await page.evaluate(() => document.activeElement?.id)).toBe('focusable');
expect.soft(await page.evaluate(() => window['eventLog'])).toEqual([
'focus focusable',
]);
});

View file

@ -532,6 +532,98 @@ it('should type repeatedly in contenteditable in shadow dom', async ({ page }) =
expect(await sectionEditor.textContent()).toBe('This is the second box.');
});
it('should type repeatedly in contenteditable in shadow dom with nested elements', async ({ page }) => {
await page.setContent(`
<html>
<body>
<shadow-element></shadow-element>
<script>
customElements.define('shadow-element', class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = \`
<style>
.editor { padding: 1rem; margin: 1rem; border: 1px solid #ccc; }
</style>
<div class=editor contenteditable id=foo><p>hello</p></div>
<hr>
<section>
<div class=editor contenteditable id=bar><p>world</p></div>
</section>
\`;
}
});
</script>
</body>
</html>
`);
const editor = page.locator('shadow-element > .editor').first();
await editor.type('This is the first box: ');
const sectionEditor = page.locator('section .editor');
await sectionEditor.type('This is the second box: ');
expect(await editor.textContent()).toBe('This is the first box: hello');
expect(await sectionEditor.textContent()).toBe('This is the second box: world');
});
it('should type repeatedly in input in shadow dom', async ({ page }) => {
await page.setContent(`
<html>
<body>
<shadow-element></shadow-element>
<script>
customElements.define('shadow-element', class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = \`
<style>
.editor { padding: 1rem; margin: 1rem; border: 1px solid #ccc; }
</style>
<input class=editor id=foo>
<hr>
<section>
<input class=editor id=bar>
</section>
\`;
}
});
</script>
</body>
</html>
`);
const editor = page.locator('shadow-element > .editor').first();
await editor.type('This is the first box.');
const sectionEditor = page.locator('section .editor');
await sectionEditor.type('This is the second box.');
expect(await editor.inputValue()).toBe('This is the first box.');
expect(await sectionEditor.inputValue()).toBe('This is the second box.');
});
it('type to non-focusable element should maintain old focus', async ({ page }) => {
await page.setContent(`
<div id="focusable" tabindex="0">focusable div</div>
<div id="non-focusable-and-non-editable">non-editable, non-focusable</div>
`);
await page.locator('#focusable').focus();
expect(await page.evaluate(() => document.activeElement?.id)).toBe('focusable');
await page.locator('#non-focusable-and-non-editable').type('foo');
expect(await page.evaluate(() => document.activeElement?.id)).toBe('focusable');
});
async function captureLastKeydown(page) {
const lastEvent = await page.evaluateHandle(() => {
const lastEvent = {

View file

@ -74,6 +74,42 @@ test('should disable animations by default', async ({ runInlineTest }, testInfo)
expect(result.exitCode).toBe(0);
});
test.describe('expect config animations option', () => {
test('disabled', async ({ runInlineTest }, testInfo) => {
const cssTransitionURL = pathToFileURL(path.join(__dirname, '../assets/css-transition.html'));
const result = await runInlineTest({
...playwrightConfig({
expect: { toHaveScreenshot: { animations: 'disabled' } },
}),
'a.spec.js': `
pwt.test('is a test', async ({ page }) => {
await page.goto('${cssTransitionURL}');
await expect(page).toHaveScreenshot({ timeout: 2000 });
});
`
}, { 'update-snapshots': true });
expect(result.exitCode).toBe(0);
});
test('allow', async ({ runInlineTest }, testInfo) => {
const cssTransitionURL = pathToFileURL(path.join(__dirname, '../assets/css-transition.html'));
const result = await runInlineTest({
...playwrightConfig({
expect: { toHaveScreenshot: { animations: 'allow' } },
}),
'a.spec.js': `
pwt.test('is a test', async ({ page }) => {
await page.goto('${cssTransitionURL}');
await expect(page).toHaveScreenshot({ timeout: 2000 });
});
`
}, { 'update-snapshots': true });
expect(result.exitCode).toBe(1);
expect(result.output).toContain('is-a-test-1-diff.png');
});
});
test('should fail with proper error when unsupported argument is given', async ({ runInlineTest }, testInfo) => {
const cssTransitionURL = pathToFileURL(path.join(__dirname, '../assets/css-transition.html'));
const result = await runInlineTest({
@ -403,6 +439,8 @@ test('should compile with different option combinations', async ({ runTSC }) =>
const { test } = pwt;
test('is a test', async ({ page }) => {
await expect(page).toHaveScreenshot();
await expect(page).toHaveScreenshot('img.png');
await expect(page).toHaveScreenshot('img.png', { threshold: 0.2, caret: 'initial' });
await expect(page.locator('body')).toHaveScreenshot({ threshold: 0.2 });
await expect(page).toHaveScreenshot({ maxDiffPixelRatio: 0.2 });
await expect(page).toHaveScreenshot({