Compare commits

...

21 commits

Author SHA1 Message Date
Andrey Lushnikov 46fb26d13a
chore: mark v1.26.1 (#17606) 2022-09-26 22:42:17 -07:00
Pavel Feldman 4b22c3f003 chery-pick(#17599): Revert "fix(pwt): compatibility in CWD with wrong casing (#16636)" 2022-09-26 20:13:37 -07:00
Playwright Service 6e8c2c5d8d
chery-pick(#17579): fix(driver): with CWD which contained spaces (#17593)
This PR cherry-picks the following commits:

- a5eee6d960

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2022-09-26 20:40:15 +02:00
Yury Semikhatsky 5455a5a5fb
cherry-pick(#17485): docs: add missing browser versions to 1.26 release (#17488) 2022-09-20 16:10:49 -07:00
Playwright Service 58e68ef6e0
chery-pick(#17478): docs(release-notes): fix dotnet annotation example (#17481)
This PR cherry-picks the following commits:

- 4dccba72ed

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2022-09-21 00:19:20 +02:00
Playwright Service f826040579
chery-pick(#17473): docs(release-notes): add 1.26 port release notes (#17476)
This PR cherry-picks the following commits:

- 1d5e90f30b

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2022-09-20 14:05:06 -07:00
Andrey Lushnikov 5321921712
chore: restore userAgent export from playwright-core (#17447)
This import is used in 8a8c89bea8932624dc28a525999e867fea8b1c74:

- cherry-pick(#17367): chore: rebuild components on new vite
2022-09-19 13:55:33 -07:00
Andrey Lushnikov c48b5116d8
chore: mark v1.26.0 (#17301) 2022-09-19 11:19:29 -07:00
Andrey Lushnikov ed6298793b chore: update release notes for 1.26 2022-09-19 14:14:29 -04:00
Andrey Lushnikov d25cc5cd8a Revert "feat: introduce docker integration (#16382)"
This reverts commit af042beb13.
2022-09-19 14:13:05 -04:00
Andrey Lushnikov 48e97d8b88 Revert "feat: expect(locator).toHaveAttribute to assert attribute presence (#16767)"
This reverts commit 622c73cc1e.
2022-09-19 13:55:33 -04:00
Andrey Lushnikov 90ac0f56d5 Revert "fix: support toHaveAttribute(name, options) (#16941)"
This reverts commit f30ac1d678.
2022-09-19 13:55:22 -04:00
Andrey Lushnikov 0c21331e34 Revert "test: unflake "should support boolean attribute with options" (#17024)"
This reverts commit 1dc05bd4c6.
2022-09-19 13:55:11 -04:00
Playwright Service 81c50d8d1c chery-pick(#17381): docs(python): add missing NotToBe{Visible,Editable,Enabled} params (#17382)
This PR cherry-picks the following commits:

- 85a5c690a4
2022-09-19 13:51:28 -04:00
Pavel Feldman 8a8c89bea8 cherry-pick(#17367): chore: rebuild components on new vite 2022-09-19 13:51:28 -04:00
Dmitry Gozman e1681a3a89 cherry-pick(#17349): fix(socks proxy): destroy sockets on close to avoid hanging (#17366) 2022-09-19 13:49:51 -04:00
Max Schmitt ae4c5bf125 cherry-pick(#17344): Revert "chore(generator): use new .NET test attributes (#17172)" (#17347)
This reverts commit 15add13a6a.
2022-09-19 13:49:51 -04:00
Andrey Lushnikov 0bd44ef8ad cherry-pick(#17317): fix(ignoreSnapshots): print a notice when ignoreSnapshots option is on
<img width="1161" alt="image"
src="https://user-images.githubusercontent.com/746130/190032155-ae454c3e-1a7d-4a64-8cd6-bb27f9075ef3.png">
2022-09-19 13:49:51 -04:00
Andrey Lushnikov 7d5510a2af cherry-pick(#17282): chore: add release notes for js 2022-09-19 13:49:51 -04:00
Playwright Service ce68b2fd0b
chery-pick(#17248): chore(dotnet): use csharp 10 namespace declaration (#17305)
This PR cherry-picks the following commits:

- 0b4de0df7f

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2022-09-13 18:53:10 +02:00
Playwright Service 991f2f9417
chery-pick(#17273): chore(dotnet): no nested namespace in transport channels (#17303)
This PR cherry-picks the following commits:

- c7367b7065

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2022-09-13 18:52:57 +02:00
58 changed files with 394 additions and 1052 deletions

View file

@ -103,7 +103,7 @@ namespace PlaywrightTests
private IAPIRequestContext Request = null; private IAPIRequestContext Request = null;
[PlaywrightTest] [Test]
public async Task ShouldCreateBugReport() public async Task ShouldCreateBugReport()
{ {
var data = new Dictionary<string, string>(); var data = new Dictionary<string, string>();
@ -130,7 +130,7 @@ namespace PlaywrightTests
Assert.AreEqual("Bug description", issue?.GetProperty("body").GetString()); Assert.AreEqual("Bug description", issue?.GetProperty("body").GetString());
} }
[PlaywrightTest] [Test]
public async Task ShouldCreateFeatureRequests() public async Task ShouldCreateFeatureRequests()
{ {
var data = new Dictionary<string, string>(); var data = new Dictionary<string, string>();
@ -229,7 +229,7 @@ namespace PlaywrightTests
private IAPIRequestContext Request = null; private IAPIRequestContext Request = null;
[PlaywrightTest] [Test]
public async Task ShouldCreateBugReport() public async Task ShouldCreateBugReport()
{ {
var data = new Dictionary<string, string>(); var data = new Dictionary<string, string>();
@ -256,7 +256,7 @@ namespace PlaywrightTests
Assert.AreEqual("Bug description", issue?.GetProperty("body").GetString()); Assert.AreEqual("Bug description", issue?.GetProperty("body").GetString());
} }
[PlaywrightTest] [Test]
public async Task ShouldCreateFeatureRequests() public async Task ShouldCreateFeatureRequests()
{ {
var data = new Dictionary<string, string>(); var data = new Dictionary<string, string>();
@ -343,7 +343,7 @@ The following test creates a new issue via API and then navigates to the list of
project to check that it appears at the top of the list. The check is performed using [LocatorAssertions]. project to check that it appears at the top of the list. The check is performed using [LocatorAssertions].
```csharp ```csharp
[PlaywrightTest] [Test]
public async Task LastCreatedIssueShouldBeFirstInTheList() public async Task LastCreatedIssueShouldBeFirstInTheList()
{ {
var data = new Dictionary<string, string>(); var data = new Dictionary<string, string>();
@ -366,7 +366,7 @@ The following test creates a new issue via user interface in the browser and the
it was created: it was created:
```csharp ```csharp
[PlaywrightTest] [Test]
public async Task LastCreatedIssueShouldBeOnTheServer() public async Task LastCreatedIssueShouldBeOnTheServer()
{ {
await Page.GotoAsync("https://github.com/" + USER + "/" + REPO + "/issues"); await Page.GotoAsync("https://github.com/" + USER + "/" + REPO + "/issues");

View file

@ -57,7 +57,7 @@ namespace PlaywrightTests;
[TestFixture] [TestFixture]
public class ExampleTests : PageTest public class ExampleTests : PageTest
{ {
[PlaywrightTest] [Test]
public async Task StatusBecomesSubmitted() public async Task StatusBecomesSubmitted()
{ {
// .. // ..
@ -114,6 +114,9 @@ The opposite of [`method: LocatorAssertions.toBeDisabled`].
The opposite of [`method: LocatorAssertions.toBeEditable`]. The opposite of [`method: LocatorAssertions.toBeEditable`].
### option: LocatorAssertions.NotToBeEditable.editable
* since: v1.26
- `editable` <[boolean]>
### option: LocatorAssertions.NotToBeEditable.timeout = %%-js-assertions-timeout-%% ### option: LocatorAssertions.NotToBeEditable.timeout = %%-js-assertions-timeout-%%
* since: v1.18 * since: v1.18
### option: LocatorAssertions.NotToBeEditable.timeout = %%-csharp-java-python-assertions-timeout-%% ### option: LocatorAssertions.NotToBeEditable.timeout = %%-csharp-java-python-assertions-timeout-%%
@ -136,6 +139,9 @@ The opposite of [`method: LocatorAssertions.toBeEmpty`].
The opposite of [`method: LocatorAssertions.toBeEnabled`]. The opposite of [`method: LocatorAssertions.toBeEnabled`].
### option: LocatorAssertions.NotToBeEnabled.enabled
* since: v1.26
- `enabled` <[boolean]>
### option: LocatorAssertions.NotToBeEnabled.timeout = %%-js-assertions-timeout-%% ### option: LocatorAssertions.NotToBeEnabled.timeout = %%-js-assertions-timeout-%%
* since: v1.18 * since: v1.18
### option: LocatorAssertions.NotToBeEnabled.timeout = %%-csharp-java-python-assertions-timeout-%% ### option: LocatorAssertions.NotToBeEnabled.timeout = %%-csharp-java-python-assertions-timeout-%%
@ -169,6 +175,9 @@ The opposite of [`method: LocatorAssertions.toBeHidden`].
The opposite of [`method: LocatorAssertions.toBeVisible`]. The opposite of [`method: LocatorAssertions.toBeVisible`].
### option: LocatorAssertions.NotToBeVisible.visible
* since: v1.26
- `visible` <[boolean]>
### option: LocatorAssertions.NotToBeVisible.timeout = %%-js-assertions-timeout-%% ### option: LocatorAssertions.NotToBeVisible.timeout = %%-js-assertions-timeout-%%
* since: v1.18 * since: v1.18
### option: LocatorAssertions.NotToBeVisible.timeout = %%-csharp-java-python-assertions-timeout-%% ### option: LocatorAssertions.NotToBeVisible.timeout = %%-csharp-java-python-assertions-timeout-%%
@ -205,10 +214,10 @@ Whether to use `element.innerText` instead of `element.textContent` when retriev
* since: v1.18 * since: v1.18
## async method: LocatorAssertions.NotToHaveAttribute ## async method: LocatorAssertions.NotToHaveAttribute
* since: v1.18 * since: v1.20
* langs: python * langs: python
The opposite of [`method: LocatorAssertions.toHaveAttribute#1`]. The opposite of [`method: LocatorAssertions.toHaveAttribute`].
### param: LocatorAssertions.NotToHaveAttribute.name ### param: LocatorAssertions.NotToHaveAttribute.name
* since: v1.18 * since: v1.18
@ -895,16 +904,15 @@ Whether to use `element.innerText` instead of `element.textContent` when retriev
* since: v1.18 * since: v1.18
## async method: LocatorAssertions.toHaveAttribute#1 ## async method: LocatorAssertions.toHaveAttribute
* since: v1.18 * since: v1.20
* langs: * langs:
- alias-java: hasAttribute - alias-java: hasAttribute
Ensures the [Locator] points to an element with given attribute value. Ensures the [Locator] points to an element with given attribute.
```js ```js
const locator = page.locator('input'); const locator = page.locator('input');
// Assert attribute with given value.
await expect(locator).toHaveAttribute('type', 'text'); await expect(locator).toHaveAttribute('type', 'text');
``` ```
@ -931,76 +939,23 @@ var locator = Page.Locator("input");
await Expect(locator).ToHaveAttributeAsync("type", "text"); await Expect(locator).ToHaveAttributeAsync("type", "text");
``` ```
### param: LocatorAssertions.toHaveAttribute#1.name ### param: LocatorAssertions.toHaveAttribute.name
* since: v1.18 * since: v1.18
- `name` <[string]> - `name` <[string]>
Attribute name. Attribute name.
### param: LocatorAssertions.toHaveAttribute#1.value ### param: LocatorAssertions.toHaveAttribute.value
* since: v1.18 * since: v1.18
- `value` <[string]|[RegExp]> - `value` <[string]|[RegExp]>
Expected attribute value. Expected attribute value.
### option: LocatorAssertions.toHaveAttribute#1.timeout = %%-js-assertions-timeout-%% ### option: LocatorAssertions.toHaveAttribute.timeout = %%-js-assertions-timeout-%%
* since: v1.18 * since: v1.18
### option: LocatorAssertions.toHaveAttribute#1.timeout = %%-csharp-java-python-assertions-timeout-%% ### option: LocatorAssertions.toHaveAttribute.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.18 * since: v1.18
## async method: LocatorAssertions.toHaveAttribute#2
* since: v1.26
* langs:
- alias-java: hasAttribute
Ensures the [Locator] points to an element with given attribute. The method will assert attribute
presence.
```js
const locator = page.locator('input');
// Assert attribute existance.
await expect(locator).toHaveAttribute('disabled');
await expect(locator).not.toHaveAttribute('open');
```
```java
assertThat(page.locator("input")).hasAttribute("disabled");
assertThat(page.locator("input")).not().hasAttribute("open");
```
```python async
from playwright.async_api import expect
locator = page.locator("input")
await expect(locator).to_have_attribute("disabled")
await expect(locator).not_to_have_attribute("open")
```
```python sync
from playwright.sync_api import expect
locator = page.locator("input")
expect(locator).to_have_attribute("disabled")
expect(locator).not_to_have_attribute("open")
```
```csharp
var locator = Page.Locator("input");
await Expect(locator).ToHaveAttributeAsync("disabled");
await Expect(locator).Not.ToHaveAttributeAsync("open");
```
### param: LocatorAssertions.toHaveAttribute#2.name
* since: v1.26
- `name` <[string]>
Attribute name.
### option: LocatorAssertions.toHaveAttribute#2.timeout = %%-js-assertions-timeout-%%
* since: v1.26
### option: LocatorAssertions.toHaveAttribute#2.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.26
## async method: LocatorAssertions.toHaveClass ## async method: LocatorAssertions.toHaveClass
* since: v1.20 * since: v1.20
* langs: * langs:

View file

@ -59,7 +59,7 @@ namespace PlaywrightTests;
[TestFixture] [TestFixture]
public class ExampleTests : PageTest public class ExampleTests : PageTest
{ {
[PlaywrightTest] [Test]
public async Task NavigatetoLoginPage() public async Task NavigatetoLoginPage()
{ {
// .. // ..

View file

@ -58,7 +58,7 @@ namespace PlaywrightTests;
[TestFixture] [TestFixture]
public class ExampleTests : PageTest public class ExampleTests : PageTest
{ {
[PlaywrightTest] [Test]
public async Task StatusBecomesSubmitted() public async Task StatusBecomesSubmitted()
{ {
await Page.Locator("#submit-button").ClickAsync(); await Page.Locator("#submit-button").ClickAsync();

View file

@ -176,7 +176,7 @@ steps:
name: 'Playwright Tests' name: 'Playwright Tests'
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: mcr.microsoft.com/playwright:v1.26.0-focal image: mcr.microsoft.com/playwright:v1.26.1-focal
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2
@ -194,7 +194,7 @@ steps:
name: 'Playwright Tests' name: 'Playwright Tests'
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: mcr.microsoft.com/playwright:v1.26.0-focal image: mcr.microsoft.com/playwright:v1.26.1-focal
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
@ -218,7 +218,7 @@ steps:
name: 'Playwright Tests' name: 'Playwright Tests'
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: mcr.microsoft.com/playwright:v1.26.0-focal image: mcr.microsoft.com/playwright:v1.26.1-focal
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-java@v3 - uses: actions/setup-java@v3
@ -239,7 +239,7 @@ steps:
name: 'Playwright Tests' name: 'Playwright Tests'
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: mcr.microsoft.com/playwright:v1.26.0-focal image: mcr.microsoft.com/playwright:v1.26.1-focal
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Setup dotnet - name: Setup dotnet
@ -264,7 +264,7 @@ steps:
name: 'Playwright Tests - ${{ matrix.project }} - Shard ${{ matrix.shardIndex }} of ${{ matrix.shardTotal }}' name: 'Playwright Tests - ${{ matrix.project }} - Shard ${{ matrix.shardIndex }} of ${{ matrix.shardTotal }}'
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: mcr.microsoft.com/playwright:v1.26.0-focal image: mcr.microsoft.com/playwright:v1.26.1-focal
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -299,7 +299,7 @@ jobs:
- deployment: Run_E2E_Tests - deployment: Run_E2E_Tests
pool: pool:
vmImage: ubuntu-20.04 vmImage: ubuntu-20.04
container: mcr.microsoft.com/playwright:v1.26.0-focal container: mcr.microsoft.com/playwright:v1.26.1-focal
environment: testing environment: testing
strategy: strategy:
runOnce: runOnce:
@ -325,7 +325,7 @@ jobs:
- deployment: Run_E2E_Tests - deployment: Run_E2E_Tests
pool: pool:
vmImage: ubuntu-20.04 vmImage: ubuntu-20.04
container: mcr.microsoft.com/playwright:v1.26.0-focal container: mcr.microsoft.com/playwright:v1.26.1-focal
environment: testing environment: testing
strategy: strategy:
runOnce: runOnce:
@ -368,7 +368,7 @@ Running Playwright on Circle CI is very similar to running on Github Actions. In
executors: executors:
pw-focal-development: pw-focal-development:
docker: docker:
- image: mcr.microsoft.com/playwright:v1.26.0-focal - image: mcr.microsoft.com/playwright:v1.26.1-focal
environment: environment:
NODE_ENV: development # Needed if playwright is in `devDependencies` NODE_ENV: development # Needed if playwright is in `devDependencies`
``` ```
@ -404,7 +404,7 @@ to run tests on Jenkins.
```groovy ```groovy
pipeline { pipeline {
agent { docker { image 'mcr.microsoft.com/playwright:v1.26.0-focal' } } agent { docker { image 'mcr.microsoft.com/playwright:v1.26.1-focal' } }
stages { stages {
stage('e2e-tests') { stage('e2e-tests') {
steps { steps {
@ -422,7 +422,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)). 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 ```yml
image: mcr.microsoft.com/playwright:v1.26.0-focal image: mcr.microsoft.com/playwright:v1.26.1-focal
``` ```
### GitLab CI ### GitLab CI
@ -435,7 +435,7 @@ stages:
tests: tests:
stage: test stage: test
image: mcr.microsoft.com/playwright:v1.26.0-focal image: mcr.microsoft.com/playwright:v1.26.1-focal
script: script:
... ...
``` ```
@ -451,7 +451,7 @@ stages:
tests: tests:
stage: test stage: test
image: mcr.microsoft.com/playwright:v1.26.0-focal image: mcr.microsoft.com/playwright:v1.26.1-focal
parallel: 7 parallel: 7
script: script:
- npm ci - npm ci
@ -466,7 +466,7 @@ stages:
tests: tests:
stage: test stage: test
image: mcr.microsoft.com/playwright:v1.26.0-focal image: mcr.microsoft.com/playwright:v1.26.1-focal
parallel: parallel:
matrix: matrix:
- PROJECT: ['chromium', 'webkit'] - PROJECT: ['chromium', 'webkit']

View file

@ -14,19 +14,19 @@ This image is published on [Docker Hub].
### Pull the image ### Pull the image
```bash js ```bash js
docker pull mcr.microsoft.com/playwright:v1.26.0-focal docker pull mcr.microsoft.com/playwright:v1.26.1-focal
``` ```
```bash python ```bash python
docker pull mcr.microsoft.com/playwright/python:v1.26.0-focal docker pull mcr.microsoft.com/playwright/python:v1.26.1-focal
``` ```
```bash csharp ```bash csharp
docker pull mcr.microsoft.com/playwright/dotnet:v1.26.0-focal docker pull mcr.microsoft.com/playwright/dotnet:v1.26.1-focal
``` ```
```bash java ```bash java
docker pull mcr.microsoft.com/playwright/java:v1.26.0-focal docker pull mcr.microsoft.com/playwright/java:v1.26.1-focal
``` ```
### Run the image ### 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. 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 ```bash js
docker run -it --rm --ipc=host mcr.microsoft.com/playwright:v1.26.0-focal /bin/bash docker run -it --rm --ipc=host mcr.microsoft.com/playwright:v1.26.1-focal /bin/bash
``` ```
```bash python ```bash python
docker run -it --rm --ipc=host mcr.microsoft.com/playwright/python:v1.26.0-focal /bin/bash docker run -it --rm --ipc=host mcr.microsoft.com/playwright/python:v1.26.1-focal /bin/bash
``` ```
```bash csharp ```bash csharp
docker run -it --rm --ipc=host mcr.microsoft.com/playwright/dotnet:v1.26.0-focal /bin/bash docker run -it --rm --ipc=host mcr.microsoft.com/playwright/dotnet:v1.26.1-focal /bin/bash
``` ```
```bash java ```bash java
docker run -it --rm --ipc=host mcr.microsoft.com/playwright/java:v1.26.0-focal /bin/bash docker run -it --rm --ipc=host mcr.microsoft.com/playwright/java:v1.26.1-focal /bin/bash
``` ```
#### Crawling and scraping #### Crawling and scraping
@ -58,19 +58,19 @@ docker run -it --rm --ipc=host mcr.microsoft.com/playwright/java:v1.26.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. 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 ```bash js
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright:v1.26.0-focal /bin/bash docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright:v1.26.1-focal /bin/bash
``` ```
```bash python ```bash python
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/python:v1.26.0-focal /bin/bash docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/python:v1.26.1-focal /bin/bash
``` ```
```bash csharp ```bash csharp
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/dotnet:v1.26.0-focal /bin/bash docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/dotnet:v1.26.1-focal /bin/bash
``` ```
```bash java ```bash java
docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/java:v1.26.0-focal /bin/bash docker run -it --rm --ipc=host --user pwuser --security-opt seccomp=seccomp_profile.json mcr.microsoft.com/playwright/java:v1.26.1-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: [`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:
@ -145,50 +145,3 @@ The image will be tagged as `playwright:localbuild-focal` and could be run as:
``` ```
docker run --rm -it playwright:localbuild /bin/bash docker run --rm -it playwright:localbuild /bin/bash
``` ```
## (Experimental) Playwright Test Docker Integration
* langs: js
Playwright Test now ships an **experimental** Docker integration.
With this integration, **only** browser binaries are running inside a Docker container,
while all the code is still running on the host operating system.
Docker container provides a consistent environment, eliminating browser rendering
differences across platforms. Playwright Test will automatically proxy host network traffic
into the container, so browsers can access servers running on the host.
:::note
Docker integration requires Docker installed & running on your computer.
See https://docs.docker.com/get-docker/
:::
:::note
If you use [Docker Desktop](https://www.docker.com/products/docker-desktop/), make sure to increase
default CPU and mem limit for better performance.
:::
Docker integration usage:
1. Build a local Docker image that will be used to run containers. This step
needs to be done only once.
```bash js
npx playwright docker build
```
2. Run Docker container in the background.
```bash js
npx playwright docker start
```
3. Run tests inside Docker container. Note that this command accepts all the same arguments
as a regular `npx playwright test` command.
```bash js
npx playwright docker test
```
Note that this command will detect running Docker container, and auto-launch it if needed.
4. Finally, stop Docker container when it is no longer needed.
```bash js
npx playwright docker stop
```

View file

@ -101,7 +101,7 @@ namespace PlaywrightTests;
[TestFixture] [TestFixture]
public class Tests : PageTest public class Tests : PageTest
{ {
[PlaywrightTest] [Test]
public async Task HomepageHasPlaywrightInTitleAndGetStartedLinkLinkingtoTheIntroPage() public async Task HomepageHasPlaywrightInTitleAndGetStartedLinkLinkingtoTheIntroPage()
{ {
await Page.GotoAsync("https://playwright.dev"); await Page.GotoAsync("https://playwright.dev");
@ -136,7 +136,7 @@ namespace PlaywrightTests;
[TestClass] [TestClass]
public class UnitTest1 : PageTest public class UnitTest1 : PageTest
{ {
[PlaywrightTestMethod] [TestMethod]
public async Task HomepageHasPlaywrightInTitleAndGetStartedLinkLinkingtoTheIntroPage() public async Task HomepageHasPlaywrightInTitleAndGetStartedLinkLinkingtoTheIntroPage()
{ {
await Page.GotoAsync("https://playwright.dev"); await Page.GotoAsync("https://playwright.dev");

View file

@ -4,6 +4,48 @@ title: "Release notes"
toc_max_heading_level: 2 toc_max_heading_level: 2
--- ---
## Version 1.26
### Assertions
- New option `Enabled` for [`method: LocatorAssertions.toBeEnabled`].
- [`method: LocatorAssertions.toHaveText`] now pierces open shadow roots.
- New option `Editable` for [`method: LocatorAssertions.toBeEditable`].
- New option `Visible` for [`method: LocatorAssertions.toBeVisible`].
- [`method: APIResponseAssertions.toBeOK`] is now available.
### Other highlights
- New option `MaxRedirects` for [`method: APIRequestContext.get`] and others to limit redirect count.
- Codegen now supports NUnit and MSTest frameworks.
- ASP .NET is now supported.
### Behavior Change
A bunch of Playwright APIs already support the `WaitUntil: WaitUntilState.DOMContentLoaded` option.
For example:
```csharp
await Page.GotoAsync("https://playwright.dev", new() { WaitUntil = WaitUntilState.DOMContentLoaded });
```
Prior to 1.26, this would wait for all iframes to fire the `DOMContentLoaded`
event.
To align with web specification, the `WaitUntilState.DOMContentLoaded` value only waits for
the target frame to fire the `'DOMContentLoaded'` event. Use `WaitUntil: WaitUntilState.Load` to wait for all iframes.
### Browser Versions
* Chromium 106.0.5249.30
* Mozilla Firefox 104.0
* WebKit 16.0
This version was also tested against the following stable channels:
* Google Chrome 105
* Microsoft Edge 105
## Version 1.25 ## Version 1.25
### New .runsettings file support ### New .runsettings file support
@ -263,7 +305,7 @@ namespace PlaywrightTests;
[TestFixture] [TestFixture]
public class ExampleTests : PageTest public class ExampleTests : PageTest
{ {
[PlaywrightTest] [Test]
public async Task StatusBecomesSubmitted() public async Task StatusBecomesSubmitted()
{ {
await Expect(Page.Locator(".status")).ToHaveTextAsync("Submitted"); await Expect(Page.Locator(".status")).ToHaveTextAsync("Submitted");

View file

@ -4,6 +4,46 @@ title: "Release notes"
toc_max_heading_level: 2 toc_max_heading_level: 2
--- ---
## Version 1.26
### Assertions
- New option `enabled` for [`method: LocatorAssertions.toBeEnabled`].
- [`method: LocatorAssertions.toHaveText`] now pierces open shadow roots.
- New option `editable` for [`method: LocatorAssertions.toBeEditable`].
- New option `visible` for [`method: LocatorAssertions.toBeVisible`].
### Other highlights
- New option `setMaxRedirects` for [`method: APIRequestContext.get`] and others to limit redirect count.
- Docker images are now using OpenJDK 17.
### Behavior Change
A bunch of Playwright APIs already support the `setWaitUntil(WaitUntilState.DOMCONTENTLOADED)` option.
For example:
```js
page.navigate("https://playwright.dev", new Page.NavigateOptions().setWaitUntil(WaitUntilState.DOMCONTENTLOADED));
```
Prior to 1.26, this would wait for all iframes to fire the `DOMContentLoaded`
event.
To align with web specification, the `WaitUntilState.DOMCONTENTLOADED` value only waits for
the target frame to fire the `'DOMContentLoaded'` event. Use `setWaitUntil(WaitUntilState.LOAD)` to wait for all iframes.
### Browser Versions
* Chromium 106.0.5249.30
* Mozilla Firefox 104.0
* WebKit 16.0
This version was also tested against the following stable channels:
* Google Chrome 105
* Microsoft Edge 105
## Version 1.25 ## Version 1.25
### New APIs & changes ### New APIs & changes

View file

@ -4,6 +4,49 @@ title: "Release notes"
toc_max_heading_level: 2 toc_max_heading_level: 2
--- ---
## Version 1.26
### Assertions
- New option `enabled` for [`method: LocatorAssertions.toBeEnabled`].
- [`method: LocatorAssertions.toHaveText`] now pierces open shadow roots.
- New option `editable` for [`method: LocatorAssertions.toBeEditable`].
- New option `visible` for [`method: LocatorAssertions.toBeVisible`].
### Other highlights
- New option `maxRedirects` for [`method: APIRequestContext.get`] and others to limit redirect count.
- New command-line flag `--pass-with-no-tests` that allows the test suite to pass when no files are found.
- New command-line flag `--ignore-snapshots` to skip snapshot expectations, such as `expect(value).toMatchSnapshot()` and `expect(page).toHaveScreenshot()`.
### Behavior Change
A bunch of Playwright APIs already support the `waitUntil: 'domcontentloaded'` option.
For example:
```js
await page.goto('https://playwright.dev', {
waitUntil: 'domcontentloaded',
});
```
Prior to 1.26, this would wait for all iframes to fire the `DOMContentLoaded`
event.
To align with web specification, the `'domcontentloaded'` value only waits for
the target frame to fire the `'DOMContentLoaded'` event. Use `waitUntil: 'load'` to wait for all iframes.
### Browser Versions
* Chromium 106.0.5249.30
* Mozilla Firefox 104.0
* WebKit 16.0
This version was also tested against the following stable channels:
* Google Chrome 105
* Microsoft Edge 105
## Version 1.25 ## Version 1.25
<div className="embed-youtube"> <div className="embed-youtube">
@ -38,7 +81,7 @@ toc_max_heading_level: 2
### Announcements ### Announcements
* 🎁 We now ship Ubuntu 22.04 Jammy Jellyfish docker image: `mcr.microsoft.com/playwright:v1.26.0-jammy`. * 🎁 We now ship Ubuntu 22.04 Jammy Jellyfish docker image: `mcr.microsoft.com/playwright:v1.26.1-jammy`.
* 🪦 This is the last release with macOS 10.15 support (deprecated as of 1.21). * 🪦 This is the last release with macOS 10.15 support (deprecated as of 1.21).
* 🪦 This is the last release with Node.js 12 support, we recommend upgrading to Node.js LTS (16). * 🪦 This is the last release with Node.js 12 support, we recommend upgrading to Node.js LTS (16).
* ⚠️ Ubuntu 18 is now deprecated and will not be supported as of Dec 2022. * ⚠️ Ubuntu 18 is now deprecated and will not be supported as of Dec 2022.
@ -288,7 +331,7 @@ Read more about [component testing with Playwright](./test-components).
} }
}); });
``` ```
* Playwright now runs on Ubuntu 22 amd64 and Ubuntu 22 arm64. We also publish new docker image `mcr.microsoft.com/playwright:v1.26.0-jammy`. * Playwright now runs on Ubuntu 22 amd64 and Ubuntu 22 arm64. We also publish new docker image `mcr.microsoft.com/playwright:v1.26.1-jammy`.
### ⚠️ Breaking Changes ⚠️ ### ⚠️ Breaking Changes ⚠️

View file

@ -4,11 +4,51 @@ title: "Release notes"
toc_max_heading_level: 2 toc_max_heading_level: 2
--- ---
## Version 1.26
### Assertions
- New option `enabled` for [`method: LocatorAssertions.toBeEnabled`].
- [`method: LocatorAssertions.toHaveText`] now pierces open shadow roots.
- New option `editable` for [`method: LocatorAssertions.toBeEditable`].
- New option `visible` for [`method: LocatorAssertions.toBeVisible`].
### Other highlights
- New option `max_redirects` for [`method: APIRequestContext.get`] and others to limit redirect count.
- Python 3.11 is now supported.
### Behavior Change
A bunch of Playwright APIs already support the `wait_until: "domcontentloaded"` option.
For example:
```python
page.goto("https://playwright.dev", wait_until="domcontentloaded")
```
Prior to 1.26, this would wait for all iframes to fire the `DOMContentLoaded`
event.
To align with web specification, the `'domcontentloaded'` value only waits for
the target frame to fire the `'DOMContentLoaded'` event. Use `wait_until="load"` to wait for all iframes.
### Browser Versions
* Chromium 106.0.5249.30
* Mozilla Firefox 104.0
* WebKit 16.0
This version was also tested against the following stable channels:
* Google Chrome 105
* Microsoft Edge 105
## Version 1.25 ## Version 1.25
### Announcements ### Announcements
* 🎁 We now ship Ubuntu 22.04 Jammy Jellyfish docker image: `mcr.microsoft.com/playwright/python:v1.26.0-jammy`. * 🎁 We now ship Ubuntu 22.04 Jammy Jellyfish docker image: `mcr.microsoft.com/playwright/python:v1.26.1-jammy`.
* 🪦 This is the last release with macOS 10.15 support (deprecated as of 1.21). * 🪦 This is the last release with macOS 10.15 support (deprecated as of 1.21).
* ⚠️ Ubuntu 18 is now deprecated and will not be supported as of Dec 2022. * ⚠️ Ubuntu 18 is now deprecated and will not be supported as of Dec 2022.

View file

@ -44,14 +44,14 @@ namespace PlaywrightTests;
[TestFixture] [TestFixture]
public class MyTest : PageTest public class MyTest : PageTest
{ {
[PlaywrightTest] [Test]
public async Task ShouldHaveTheCorrectSlogan() public async Task ShouldHaveTheCorrectSlogan()
{ {
await Page.GotoAsync("https://playwright.dev"); await Page.GotoAsync("https://playwright.dev");
await Expect(Page.Locator("text=enables reliable end-to-end testing for modern web apps")).ToBeVisibleAsync(); await Expect(Page.Locator("text=enables reliable end-to-end testing for modern web apps")).ToBeVisibleAsync();
} }
[PlaywrightTest] [Test]
public async Task ShouldHaveTheCorrectTitle() public async Task ShouldHaveTheCorrectTitle()
{ {
await Page.GotoAsync("https://playwright.dev"); await Page.GotoAsync("https://playwright.dev");
@ -125,7 +125,7 @@ namespace PlaywrightTests;
[TestFixture] [TestFixture]
public class MyTest : PageTest public class MyTest : PageTest
{ {
[PlaywrightTest] [Test]
public async Task TestWithCustomContextOptions() public async Task TestWithCustomContextOptions()
{ {
// The following Page (and BrowserContext) instance has the custom colorScheme, viewport and baseURL set: // The following Page (and BrowserContext) instance has the custom colorScheme, viewport and baseURL set:
@ -217,10 +217,6 @@ There are a few base classes available to you in `Microsoft.Playwright.NUnit` na
|BrowserTest |Each test will get a browser and can create as many contexts as it likes. Each test is responsible for cleaning up all the contexts it created.| |BrowserTest |Each test will get a browser and can create as many contexts as it likes. Each test is responsible for cleaning up all the contexts it created.|
|PlaywrightTest|This gives each test a Playwright object so that the test could start and stop as many browsers as it likes.| |PlaywrightTest|This gives each test a Playwright object so that the test could start and stop as many browsers as it likes.|
### 'No test is available'
You need to add `[TestFixture]` to your test class. NUnit does not discover tests without it, if the `TestAttribute` comes from a different assembly.
## MSTest ## MSTest
Playwright provides base classes to write tests with MSTest via the [`Microsoft.Playwright.MSTest`](https://www.nuget.org/packages/Microsoft.Playwright.MSTest) package. Playwright provides base classes to write tests with MSTest via the [`Microsoft.Playwright.MSTest`](https://www.nuget.org/packages/Microsoft.Playwright.MSTest) package.
@ -250,14 +246,14 @@ namespace PlaywrightTests;
[TestClass] [TestClass]
public class UnitTest1: PageTest public class UnitTest1: PageTest
{ {
[PlaywrightTestMethod] [TestMethod]
public async Task ShouldHaveTheCorrectSlogan() public async Task ShouldHaveTheCorrectSlogan()
{ {
await Page.GotoAsync("https://playwright.dev"); await Page.GotoAsync("https://playwright.dev");
await Expect(Page.Locator("text=enables reliable end-to-end testing for modern web apps")).ToBeVisibleAsync(); await Expect(Page.Locator("text=enables reliable end-to-end testing for modern web apps")).ToBeVisibleAsync();
} }
[PlaywrightTestMethod] [TestMethod]
public async Task ShouldHaveTheCorrectTitle() public async Task ShouldHaveTheCorrectTitle()
{ {
await Page.GotoAsync("https://playwright.dev"); await Page.GotoAsync("https://playwright.dev");
@ -335,7 +331,7 @@ namespace PlaywrightTests;
[TestClass] [TestClass]
public class UnitTest1 : PageTest public class UnitTest1 : PageTest
{ {
[PlaywrightTestMethod] [TestMethod]
public async Task TestWithCustomContextOptions() public async Task TestWithCustomContextOptions()
{ {
// The following Page (and BrowserContext) instance has the custom colorScheme, viewport and baseURL set: // The following Page (and BrowserContext) instance has the custom colorScheme, viewport and baseURL set:

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: If you are not on the same operating system as your CI system, you can use Docker to generate/update the screenshots:
```bash ```bash
docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.26.0-focal /bin/bash docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.26.1-focal /bin/bash
npm install npm install
npx playwright test --update-snapshots npx playwright test --update-snapshots
``` ```

View file

@ -27,7 +27,7 @@ namespace PlaywrightTests;
[TestFixture] [TestFixture]
public class Tests : PageTest public class Tests : PageTest
{ {
[PlaywrightTest] [Test]
public async Task HomepageHasPlaywrightInTitleAndGetStartedLinkLinkingtoTheIntroPage() public async Task HomepageHasPlaywrightInTitleAndGetStartedLinkLinkingtoTheIntroPage()
{ {
await Page.GotoAsync("https://playwright.dev"); await Page.GotoAsync("https://playwright.dev");
@ -62,7 +62,7 @@ namespace PlaywrightTests;
[TestClass] [TestClass]
public class UnitTest1 : PageTest public class UnitTest1 : PageTest
{ {
[PlaywrightTestMethod] [TestMethod]
public async Task HomepageHasPlaywrightInTitleAndGetStartedLinkLinkingtoTheIntroPage() public async Task HomepageHasPlaywrightInTitleAndGetStartedLinkLinkingtoTheIntroPage()
{ {
await Page.GotoAsync("https://playwright.dev"); await Page.GotoAsync("https://playwright.dev");
@ -140,7 +140,7 @@ namespace PlaywrightTests;
[TestFixture] [TestFixture]
public class Tests : PageTest public class Tests : PageTest
{ {
[PlaywrightTest] [Test]
public async Task BasicTest() public async Task BasicTest()
{ {
await Page.GotoAsync("https://playwright.dev"); await Page.GotoAsync("https://playwright.dev");
@ -159,7 +159,7 @@ namespace PlaywrightTests;
[TestClass] [TestClass]
public class UnitTest1 : PageTest public class UnitTest1 : PageTest
{ {
[PlaywrightTestMethod] [TestMethod]
public async Task BasicTest() public async Task BasicTest()
{ {
await Page.GotoAsync("https://playwright.dev"); await Page.GotoAsync("https://playwright.dev");
@ -195,7 +195,7 @@ namespace PlaywrightTests;
[TestFixture] [TestFixture]
public class Tests : PageTest public class Tests : PageTest
{ {
[PlaywrightTest] [Test]
public async Task MainNavigation() public async Task MainNavigation()
{ {
// Assertions use the expect API. // Assertions use the expect API.
@ -221,7 +221,7 @@ namespace PlaywrightTests;
[TestClass] [TestClass]
public class UnitTest1 : PageTest public class UnitTest1 : PageTest
{ {
[PlaywrightTestMethod] [TestMethod]
public async Task MainNavigation() public async Task MainNavigation()
{ {
// Assertions use the expect API. // Assertions use the expect API.

66
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "playwright-internal", "name": "playwright-internal",
"version": "1.26.0-next", "version": "1.26.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "playwright-internal", "name": "playwright-internal",
"version": "1.26.0-next", "version": "1.26.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
@ -6383,11 +6383,11 @@
"version": "0.0.0" "version": "0.0.0"
}, },
"packages/playwright": { "packages/playwright": {
"version": "1.26.0-next", "version": "1.26.1",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -6397,11 +6397,11 @@
} }
}, },
"packages/playwright-chromium": { "packages/playwright-chromium": {
"version": "1.26.0-next", "version": "1.26.1",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -6411,7 +6411,7 @@
} }
}, },
"packages/playwright-core": { "packages/playwright-core": {
"version": "1.26.0-next", "version": "1.26.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -6422,10 +6422,10 @@
}, },
"packages/playwright-ct-react": { "packages/playwright-ct-react": {
"name": "@playwright/experimental-ct-react", "name": "@playwright/experimental-ct-react",
"version": "1.26.0-next", "version": "1.26.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"@vitejs/plugin-react": "^2.0.1", "@vitejs/plugin-react": "^2.0.1",
"vite": "^3.0.9" "vite": "^3.0.9"
}, },
@ -6878,10 +6878,10 @@
}, },
"packages/playwright-ct-solid": { "packages/playwright-ct-solid": {
"name": "@playwright/experimental-ct-solid", "name": "@playwright/experimental-ct-solid",
"version": "1.26.0-next", "version": "1.26.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"vite": "^3.0.0", "vite": "^3.0.0",
"vite-plugin-solid": "^2.3.0" "vite-plugin-solid": "^2.3.0"
}, },
@ -7298,10 +7298,10 @@
}, },
"packages/playwright-ct-svelte": { "packages/playwright-ct-svelte": {
"name": "@playwright/experimental-ct-svelte", "name": "@playwright/experimental-ct-svelte",
"version": "1.26.0-next", "version": "1.26.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"@sveltejs/vite-plugin-svelte": "^1.0.1", "@sveltejs/vite-plugin-svelte": "^1.0.1",
"vite": "^3.0.0" "vite": "^3.0.0"
}, },
@ -7728,10 +7728,10 @@
}, },
"packages/playwright-ct-vue": { "packages/playwright-ct-vue": {
"name": "@playwright/experimental-ct-vue", "name": "@playwright/experimental-ct-vue",
"version": "1.26.0-next", "version": "1.26.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"@vitejs/plugin-vue": "^2.3.1", "@vitejs/plugin-vue": "^2.3.1",
"vite": "^2.9.5" "vite": "^2.9.5"
}, },
@ -7776,10 +7776,10 @@
}, },
"packages/playwright-ct-vue2": { "packages/playwright-ct-vue2": {
"name": "@playwright/experimental-ct-vue2", "name": "@playwright/experimental-ct-vue2",
"version": "1.26.0-next", "version": "1.26.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"vite": "^2.9.5", "vite": "^2.9.5",
"vite-plugin-vue2": "^2.0.1" "vite-plugin-vue2": "^2.0.1"
}, },
@ -7791,11 +7791,11 @@
} }
}, },
"packages/playwright-firefox": { "packages/playwright-firefox": {
"version": "1.26.0-next", "version": "1.26.1",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -7806,11 +7806,11 @@
}, },
"packages/playwright-test": { "packages/playwright-test": {
"name": "@playwright/test", "name": "@playwright/test",
"version": "1.26.0-next", "version": "1.26.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -7820,11 +7820,11 @@
} }
}, },
"packages/playwright-webkit": { "packages/playwright-webkit": {
"version": "1.26.0-next", "version": "1.26.1",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@ -8515,7 +8515,7 @@
"@playwright/experimental-ct-react": { "@playwright/experimental-ct-react": {
"version": "file:packages/playwright-ct-react", "version": "file:packages/playwright-ct-react",
"requires": { "requires": {
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"@vitejs/plugin-react": "^2.0.1", "@vitejs/plugin-react": "^2.0.1",
"vite": "^3.0.9" "vite": "^3.0.9"
}, },
@ -8724,7 +8724,7 @@
"@playwright/experimental-ct-solid": { "@playwright/experimental-ct-solid": {
"version": "file:packages/playwright-ct-solid", "version": "file:packages/playwright-ct-solid",
"requires": { "requires": {
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"solid-js": "^1.4.7", "solid-js": "^1.4.7",
"vite": "^3.0.0", "vite": "^3.0.0",
"vite-plugin-solid": "^2.3.0" "vite-plugin-solid": "^2.3.0"
@ -8909,7 +8909,7 @@
"@playwright/experimental-ct-svelte": { "@playwright/experimental-ct-svelte": {
"version": "file:packages/playwright-ct-svelte", "version": "file:packages/playwright-ct-svelte",
"requires": { "requires": {
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"@sveltejs/vite-plugin-svelte": "^1.0.1", "@sveltejs/vite-plugin-svelte": "^1.0.1",
"svelte": "^3.49.0", "svelte": "^3.49.0",
"vite": "^3.0.0" "vite": "^3.0.0"
@ -9095,7 +9095,7 @@
"@playwright/experimental-ct-vue": { "@playwright/experimental-ct-vue": {
"version": "file:packages/playwright-ct-vue", "version": "file:packages/playwright-ct-vue",
"requires": { "requires": {
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"@vitejs/plugin-vue": "^2.3.1", "@vitejs/plugin-vue": "^2.3.1",
"vite": "^2.9.5" "vite": "^2.9.5"
}, },
@ -9128,7 +9128,7 @@
"@playwright/experimental-ct-vue2": { "@playwright/experimental-ct-vue2": {
"version": "file:packages/playwright-ct-vue2", "version": "file:packages/playwright-ct-vue2",
"requires": { "requires": {
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"vite": "^2.9.5", "vite": "^2.9.5",
"vite-plugin-vue2": "^2.0.1", "vite-plugin-vue2": "^2.0.1",
"vue": "^2.6.14" "vue": "^2.6.14"
@ -9138,7 +9138,7 @@
"version": "file:packages/playwright-test", "version": "file:packages/playwright-test",
"requires": { "requires": {
"@types/node": "*", "@types/node": "*",
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
} }
}, },
"@rollup/pluginutils": { "@rollup/pluginutils": {
@ -11355,13 +11355,13 @@
"playwright": { "playwright": {
"version": "file:packages/playwright", "version": "file:packages/playwright",
"requires": { "requires": {
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
} }
}, },
"playwright-chromium": { "playwright-chromium": {
"version": "file:packages/playwright-chromium", "version": "file:packages/playwright-chromium",
"requires": { "requires": {
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
} }
}, },
"playwright-core": { "playwright-core": {
@ -11370,13 +11370,13 @@
"playwright-firefox": { "playwright-firefox": {
"version": "file:packages/playwright-firefox", "version": "file:packages/playwright-firefox",
"requires": { "requires": {
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
} }
}, },
"playwright-webkit": { "playwright-webkit": {
"version": "file:packages/playwright-webkit", "version": "file:packages/playwright-webkit",
"requires": { "requires": {
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
} }
}, },
"postcss": { "postcss": {

View file

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

View file

@ -1,6 +1,6 @@
{ {
"name": "playwright-chromium", "name": "playwright-chromium",
"version": "1.26.0-next", "version": "1.26.1",
"description": "A high-level API to automate Chromium", "description": "A high-level API to automate Chromium",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -28,6 +28,6 @@
"install": "node install.js" "install": "node install.js"
}, },
"dependencies": { "dependencies": {
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "playwright-core", "name": "playwright-core",
"version": "1.26.0-next", "version": "1.26.1",
"description": "A high-level API to automate web browsers", "description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -23,8 +23,8 @@
"./lib/grid/gridServer": "./lib/grid/gridServer.js", "./lib/grid/gridServer": "./lib/grid/gridServer.js",
"./lib/outofprocess": "./lib/outofprocess.js", "./lib/outofprocess": "./lib/outofprocess.js",
"./lib/utils": "./lib/utils/index.js", "./lib/utils": "./lib/utils/index.js",
"./lib/common/userAgent": "./lib/common/userAgent.js",
"./lib/utils/comparators": "./lib/utils/comparators.js", "./lib/utils/comparators": "./lib/utils/comparators.js",
"./lib/common/userAgent": "./lib/common/userAgent.js",
"./lib/utils/eventsHelper": "./lib/utils/eventsHelper.js", "./lib/utils/eventsHelper": "./lib/utils/eventsHelper.js",
"./lib/utils/fileUtils": "./lib/utils/fileUtils.js", "./lib/utils/fileUtils": "./lib/utils/fileUtils.js",
"./lib/utils/httpServer": "./lib/utils/httpServer.js", "./lib/utils/httpServer": "./lib/utils/httpServer.js",

View file

@ -291,7 +291,7 @@ program
program program
.command('show-trace [trace...]') .command('show-trace [trace...]')
.option('-b, --browser <browserType>', 'browser to use, one of cr, chromium, ff, firefox, wk, webkit', 'chromium') .option('-b, --browser <browserType>', 'browser to use, one of cr, chromium, ff, firefox, wk, webkit', 'chromium')
.description('show trace viewer') .description('Show trace viewer')
.action(function(traces, options) { .action(function(traces, options) {
if (options.browser === 'cr') if (options.browser === 'cr')
options.browser = 'chromium'; options.browser = 'chromium';

View file

@ -294,6 +294,8 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
private _server: net.Server; private _server: net.Server;
private _connections = new Map<string, SocksConnection>(); private _connections = new Map<string, SocksConnection>();
private _sockets = new Set<net.Socket>();
private _closed = false;
constructor() { constructor() {
super(); super();
@ -302,6 +304,14 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
const connection = new SocksConnection(uid, socket, this); const connection = new SocksConnection(uid, socket, this);
this._connections.set(uid, connection); this._connections.set(uid, connection);
}); });
this._server.on('connection', socket => {
if (this._closed) {
socket.destroy();
return;
}
this._sockets.add(socket);
socket.once('close', () => this._sockets.delete(socket));
});
} }
async listen(port: number): Promise<number> { async listen(port: number): Promise<number> {
@ -315,6 +325,10 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
} }
async close() { async close() {
this._closed = true;
for (const socket of this._sockets)
socket.destroy();
this._sockets.clear();
await new Promise(f => this._server.close(f)); await new Promise(f => this._server.close(f));
} }

View file

@ -75,7 +75,7 @@ export function getClientLanguage(): { langName: string, langVersion: string } {
return { langName, langVersion }; return { langName, langVersion };
} }
export function getPlaywrightVersion(majorMinorOnly = false) { export function getPlaywrightVersion(majorMinorOnly = false): string {
const packageJson = require('./../../package.json'); const packageJson = require('./../../package.json');
return majorMinorOnly ? packageJson.version.split('.').slice(0, 2).join('.') : packageJson.version; return majorMinorOnly ? packageJson.version.split('.').slice(0, 2).join('.') : packageJson.version;
} }

View file

@ -1038,9 +1038,7 @@ export class InjectedScript {
{ {
// Element state / boolean values. // Element state / boolean values.
let elementState: boolean | 'error:notconnected' | 'error:notcheckbox' | undefined; let elementState: boolean | 'error:notconnected' | 'error:notcheckbox' | undefined;
if (expression === 'to.have.attribute') { if (expression === 'to.be.checked') {
elementState = element.hasAttribute(options.expressionArg);
} else if (expression === 'to.be.checked') {
elementState = progress.injectedScript.elementState(element, 'checked'); elementState = progress.injectedScript.elementState(element, 'checked');
} else if (expression === 'to.be.unchecked') { } else if (expression === 'to.be.unchecked') {
elementState = progress.injectedScript.elementState(element, 'unchecked'); elementState = progress.injectedScript.elementState(element, 'unchecked');
@ -1100,7 +1098,7 @@ export class InjectedScript {
{ {
// Single text value. // Single text value.
let received: string | undefined; let received: string | undefined;
if (expression === 'to.have.attribute.value') { if (expression === 'to.have.attribute') {
received = element.getAttribute(options.expressionArg) || ''; received = element.getAttribute(options.expressionArg) || '';
} else if (expression === 'to.have.class') { } else if (expression === 'to.have.class') {
received = element.classList.toString(); received = element.classList.toString();

View file

@ -207,7 +207,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
}`); }`);
formatter.newLine(); formatter.newLine();
} }
formatter.add(` [${this._mode === 'nunit' ? 'PlaywrightTest' : 'PlaywrightTestMethod'}] formatter.add(` [${this._mode === 'nunit' ? 'Test' : 'TestMethod'}]
public async Task MyTest() public async Task MyTest()
{`); {`);
return formatter.format(); return formatter.format();

View file

@ -1,6 +1,6 @@
{ {
"name": "@playwright/experimental-ct-react", "name": "@playwright/experimental-ct-react",
"version": "1.26.0-next", "version": "1.26.1",
"description": "Playwright Component Testing for React", "description": "Playwright Component Testing for React",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -27,7 +27,7 @@
}, },
"dependencies": { "dependencies": {
"@vitejs/plugin-react": "^2.0.1", "@vitejs/plugin-react": "^2.0.1",
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"vite": "^3.0.9" "vite": "^3.0.9"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@playwright/experimental-ct-solid", "name": "@playwright/experimental-ct-solid",
"version": "1.26.0-next", "version": "1.26.1",
"description": "Playwright Component Testing for Solid", "description": "Playwright Component Testing for Solid",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -28,7 +28,7 @@
"dependencies": { "dependencies": {
"vite": "^3.0.0", "vite": "^3.0.0",
"vite-plugin-solid": "^2.3.0", "vite-plugin-solid": "^2.3.0",
"@playwright/test": "1.26.0-next" "@playwright/test": "1.26.1"
}, },
"devDependencies": { "devDependencies": {
"solid-js": "^1.4.7" "solid-js": "^1.4.7"

View file

@ -1,6 +1,6 @@
{ {
"name": "@playwright/experimental-ct-svelte", "name": "@playwright/experimental-ct-svelte",
"version": "1.26.0-next", "version": "1.26.1",
"description": "Playwright Component Testing for Svelte", "description": "Playwright Component Testing for Svelte",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -26,7 +26,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"@sveltejs/vite-plugin-svelte": "^1.0.1", "@sveltejs/vite-plugin-svelte": "^1.0.1",
"vite": "^3.0.0" "vite": "^3.0.0"
}, },

View file

@ -1,6 +1,6 @@
{ {
"name": "@playwright/experimental-ct-vue", "name": "@playwright/experimental-ct-vue",
"version": "1.26.0-next", "version": "1.26.1",
"description": "Playwright Component Testing for Vue", "description": "Playwright Component Testing for Vue",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -27,7 +27,7 @@
}, },
"dependencies": { "dependencies": {
"@vitejs/plugin-vue": "^2.3.1", "@vitejs/plugin-vue": "^2.3.1",
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"vite": "^2.9.5" "vite": "^2.9.5"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@playwright/experimental-ct-vue2", "name": "@playwright/experimental-ct-vue2",
"version": "1.26.0-next", "version": "1.26.1",
"description": "Playwright Component Testing for Vue2", "description": "Playwright Component Testing for Vue2",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -26,7 +26,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@playwright/test": "1.26.0-next", "@playwright/test": "1.26.1",
"vite": "^2.9.5", "vite": "^2.9.5",
"vite-plugin-vue2": "^2.0.1" "vite-plugin-vue2": "^2.0.1"
}, },

View file

@ -1,6 +1,6 @@
{ {
"name": "playwright-firefox", "name": "playwright-firefox",
"version": "1.26.0-next", "version": "1.26.1",
"description": "A high-level API to automate Firefox", "description": "A high-level API to automate Firefox",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -28,6 +28,6 @@
"install": "node install.js" "install": "node install.js"
}, },
"dependencies": { "dependencies": {
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@playwright/test", "name": "@playwright/test",
"version": "1.26.0-next", "version": "1.26.1",
"description": "A high-level API to automate web browsers", "description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -34,6 +34,6 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
} }
} }

View file

@ -1,6 +1,5 @@
[*] [*]
./utilsBundle.ts ./utilsBundle.ts
docker/
matchers/ matchers/
reporters/ reporters/
third_party/ third_party/

View file

@ -17,11 +17,9 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import type { Command } from 'playwright-core/lib/utilsBundle'; import type { Command } from 'playwright-core/lib/utilsBundle';
import * as docker from './docker/docker';
import fs from 'fs'; import fs from 'fs';
import url from 'url'; import url from 'url';
import path from 'path'; import path from 'path';
import { colors } from 'playwright-core/lib/utilsBundle';
import { Runner, builtInReporters, kDefaultConfigFiles } from './runner'; import { Runner, builtInReporters, kDefaultConfigFiles } from './runner';
import type { ConfigCLIOverrides } from './runner'; import type { ConfigCLIOverrides } from './runner';
import { stopProfiling, startProfiling } from './profiler'; import { stopProfiling, startProfiling } from './profiler';
@ -31,67 +29,14 @@ import { baseFullConfig, defaultTimeout, fileIsModule } from './loader';
import type { TraceMode } from './types'; import type { TraceMode } from './types';
export function addTestCommands(program: Command) { export function addTestCommands(program: Command) {
addTestCommand(program, false /* isDocker */); addTestCommand(program);
addShowReportCommand(program); addShowReportCommand(program);
addListFilesCommand(program); addListFilesCommand(program);
addDockerCommand(program);
} }
function addDockerCommand(program: Command) { function addTestCommand(program: Command) {
const dockerCommand = program.command('docker')
.description(`run tests in Docker (EXPERIMENTAL)`);
dockerCommand.command('build')
.description('build local docker image')
.action(async function(options) {
await docker.ensureDockerEngineIsRunningOrDie();
await docker.buildImage();
});
dockerCommand.command('start')
.description('start docker container')
.action(async function(options) {
await docker.ensureDockerEngineIsRunningOrDie();
let info = await docker.containerInfo();
if (!info) {
process.stdout.write(`Starting docker container... `);
const time = Date.now();
info = await docker.ensureContainerOrDie();
const deltaMs = (Date.now() - time);
console.log('Done in ' + (deltaMs / 1000).toFixed(1) + 's');
}
console.log([
`- VNC session: ${info.vncSession}`,
`- Run tests with browsers inside container:`,
` npx playwright docker test`,
`- Stop container *manually* when it is no longer needed:`,
` npx playwright docker stop`,
].join('\n'));
});
dockerCommand.command('delete-image', { hidden: true })
.description('delete docker image, if any')
.action(async function(options) {
await docker.ensureDockerEngineIsRunningOrDie();
await docker.deleteImage();
});
dockerCommand.command('stop')
.description('stop docker container')
.action(async function(options) {
await docker.ensureDockerEngineIsRunningOrDie();
await docker.stopContainer();
});
addTestCommand(dockerCommand, true /* isDocker */);
}
function addTestCommand(program: Command, isDocker: boolean) {
const command = program.command('test [test-filter...]'); const command = program.command('test [test-filter...]');
if (isDocker) command.description('Run tests with Playwright Test');
command.description('run tests with Playwright Test and browsers inside docker container');
else
command.description('run tests with Playwright Test');
command.option('--browser <browser>', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`); command.option('--browser <browser>', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`);
command.option('--headed', `Run tests in headed browsers (default: headless)`); command.option('--headed', `Run tests in headed browsers (default: headless)`);
command.option('--debug', `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --maxFailures=1 --headed --workers=1" options`); command.option('--debug', `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --maxFailures=1 --headed --workers=1" options`);
@ -119,27 +64,6 @@ function addTestCommand(program: Command, isDocker: boolean) {
command.option('-x', `Stop after the first failure`); command.option('-x', `Stop after the first failure`);
command.action(async (args, opts) => { command.action(async (args, opts) => {
try { try {
if (isDocker && !process.env.PW_TS_ESM_ON) {
console.log(colors.dim('Using docker container to run browsers.'));
await docker.ensureDockerEngineIsRunningOrDie();
let info = await docker.containerInfo();
if (!info) {
process.stdout.write(colors.dim(`Starting docker container... `));
const time = Date.now();
info = await docker.ensureContainerOrDie();
const deltaMs = (Date.now() - time);
console.log(colors.dim('Done in ' + (deltaMs / 1000).toFixed(1) + 's'));
console.log(colors.dim('The Docker container will keep running after tests finished.'));
console.log(colors.dim('Stop manually using:'));
console.log(colors.dim(' npx playwright docker stop'));
}
console.log(colors.dim(`View screen: ${info.vncSession}`));
process.env.PW_TEST_CONNECT_WS_ENDPOINT = info.wsEndpoint;
process.env.PW_TEST_CONNECT_HEADERS = JSON.stringify({
'x-playwright-proxy': '*',
});
process.env.PW_TEST_SNAPSHOT_SUFFIX = 'docker';
}
await runTests(args, opts); await runTests(args, opts);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@ -151,10 +75,10 @@ Arguments [test-filter...]:
Pass arguments to filter test files. Each argument is treated as a regular expression. Pass arguments to filter test files. Each argument is treated as a regular expression.
Examples: Examples:
$ npx playwright${isDocker ? ' docker ' : ' '}test my.spec.ts $ npx playwright test my.spec.ts
$ npx playwright${isDocker ? ' docker ' : ' '}test some.spec.ts:42 $ npx playwright test some.spec.ts:42
$ npx playwright${isDocker ? ' docker ' : ' '}test --headed $ npx playwright test --headed
$ npx playwright${isDocker ? ' docker ' : ' '}test --browser=webkit`); $ npx playwright test --browser=webkit`);
} }
function addListFilesCommand(program: Command) { function addListFilesCommand(program: Command) {

View file

@ -1,74 +0,0 @@
export NOVNC_REF='1.3.0'
export WEBSOCKIFY_REF='0.10.0'
export DEBIAN_FRONTEND=noninteractive
# Install FluxBox, VNC & noVNC
mkdir -p /opt/bin && chmod +x /dev/shm \
&& apt-get update && apt-get install -y unzip fluxbox x11vnc \
&& curl -L -o noVNC.zip "https://github.com/novnc/noVNC/archive/v${NOVNC_REF}.zip" \
&& unzip -x noVNC.zip \
&& rm -rf noVNC-${NOVNC_REF}/{docs,tests} \
&& mv noVNC-${NOVNC_REF} /opt/bin/noVNC \
&& cp /opt/bin/noVNC/vnc.html /opt/bin/noVNC/index.html \
&& rm noVNC.zip \
&& curl -L -o websockify.zip "https://github.com/novnc/websockify/archive/v${WEBSOCKIFY_REF}.zip" \
&& unzip -x websockify.zip \
&& rm websockify.zip \
&& rm -rf websockify-${WEBSOCKIFY_REF}/{docs,tests} \
&& mv websockify-${WEBSOCKIFY_REF} /opt/bin/noVNC/utils/websockify
# Configure FluxBox menus
mkdir /root/.fluxbox
cd /ms-playwright-agent
cat <<'EOF' | node > /root/.fluxbox/menu
const { chromium, firefox, webkit } = require('playwright-core');
console.log(`
[begin] (fluxbox)
[submenu] (Browsers) {}
[exec] (Chromium) { ${chromium.executablePath()} --no-sandbox --test-type= } <>
[exec] (Firefox) { ${firefox.executablePath()} } <>
[exec] (WebKit) { ${webkit.executablePath()} } <>
[end]
[include] (/etc/X11/fluxbox/fluxbox-menu)
[end]
`);
EOF
# Create entrypoint.sh
cat <<'EOF' > /entrypoint.sh
#!/bin/bash
set -e
SCREEN_WIDTH=1360
SCREEN_HEIGHT=1020
SCREEN_DEPTH=24
SCREEN_DPI=96
GEOMETRY="$SCREEN_WIDTH""x""$SCREEN_HEIGHT""x""$SCREEN_DEPTH"
nohup /usr/bin/xvfb-run --server-num=$DISPLAY_NUM \
--listen-tcp \
--server-args="-screen 0 "$GEOMETRY" -fbdir /var/tmp -dpi "$SCREEN_DPI" -listen tcp -noreset -ac +extension RANDR" \
/usr/bin/fluxbox -display "$DISPLAY" >/dev/null 2>&1 &
for i in $(seq 1 500); do
if xdpyinfo -display $DISPLAY >/dev/null 2>&1; then
break
fi
echo "Waiting for Xvfb..."
sleep 0.2
done
nohup x11vnc -forever -shared -rfbport 5900 -rfbportv6 5900 -display "$DISPLAY" >/dev/null 2>&1 &
nohup /opt/bin/noVNC/utils/novnc_proxy --listen 7900 --vnc localhost:5900 >/dev/null 2>&1 &
cd /ms-playwright-agent
fbsetbg -c /ms-playwright-agent/node_modules/playwright-core/lib/server/chromium/appIcon.png
NOVNC_UUID=$(cat /proc/sys/kernel/random/uuid)
echo "novnc is listening on http://127.0.0.1:7900?path=$NOVNC_UUID&resize=scale&autoconnect=1"
PW_UUID=$(cat /proc/sys/kernel/random/uuid)
npx playwright run-server --port=5400 --path=/$PW_UUID
EOF
chmod 755 /entrypoint.sh

View file

@ -1,303 +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.
*/
/* eslint-disable no-console */
import http from 'http';
import path from 'path';
import fs from 'fs';
import { spawnAsync } from 'playwright-core/lib/utils/spawnAsync';
import * as utils from 'playwright-core/lib/utils';
import { getPlaywrightVersion } from 'playwright-core/lib/common/userAgent';
interface DockerImage {
Containers: number;
Created: number;
Id: string;
Labels: null | Record<string, string>;
ParentId: string;
RepoDigests: null | string[];
RepoTags: null | string[];
SharedSize: number;
Size: number;
VirtualSize: number;
}
const VRT_IMAGE_DISTRO = 'focal';
const VRT_IMAGE_NAME = `playwright:local-${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}`;
const VRT_CONTAINER_NAME = `playwright-${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}`;
export async function deleteImage() {
const dockerImage = await findDockerImage(VRT_IMAGE_NAME);
if (!dockerImage)
return;
if (await containerInfo())
await stopContainer();
await callDockerAPI('delete', `/images/${dockerImage.Id}`);
}
export async function buildImage() {
const isDevelopmentMode = getPlaywrightVersion().includes('next');
let baseImageName = `mcr.microsoft.com/playwright:v${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}`;
// 1. Build or pull base image.
if (isDevelopmentMode) {
// Use our docker build scripts in development mode!
if (!process.env.PWTEST_DOCKER_BASE_IMAGE) {
const arch = process.arch === 'arm64' ? '--arm64' : '--amd64';
console.error(utils.wrapInASCIIBox([
`You are in DEVELOPMENT mode!`,
``,
`1. Build local base image`,
` ./utils/docker/build.sh ${arch} ${VRT_IMAGE_DISTRO} playwright:localbuild`,
`2. Use the local base to build VRT image:`,
` PWTEST_DOCKER_BASE_IMAGE=playwright:localbuild npx playwright docker build`,
].join('\n'), 1));
process.exit(1);
}
baseImageName = process.env.PWTEST_DOCKER_BASE_IMAGE;
} else {
const { code } = await spawnAsync('docker', ['pull', baseImageName], { stdio: 'inherit' });
if (code !== 0)
throw new Error('Failed to pull docker image!');
}
// 2. Find pulled docker image
const dockerImage = await findDockerImage(baseImageName);
if (!dockerImage)
throw new Error(`Failed to pull ${baseImageName}`);
// 3. Launch container and install VNC in it
console.log(`Building ${VRT_IMAGE_NAME}...`);
const buildScriptText = await fs.promises.readFile(path.join(__dirname, 'build_docker_image.sh'), 'utf8');
const containerId = await launchContainer({
image: dockerImage,
autoRemove: false,
command: ['/bin/bash', '-c', buildScriptText],
});
await postJSON(`/containers/${containerId}/wait`);
// 4. Commit a new image based on the launched container with installed VNC & noVNC.
const [vrtRepo, vrtTag] = VRT_IMAGE_NAME.split(':');
await postJSON(`/commit?container=${containerId}&repo=${vrtRepo}&tag=${vrtTag}`, {
Entrypoint: ['/entrypoint.sh'],
Env: [
'DISPLAY_NUM=99',
'DISPLAY=:99',
],
});
await Promise.all([
// Make sure to wait for the container to be removed.
postJSON(`/containers/${containerId}/wait?condition=removed`),
callDockerAPI('delete', `/containers/${containerId}`),
]);
console.log(`Done!`);
}
interface ContainerInfo {
wsEndpoint: string;
vncSession: string;
}
export async function containerInfo(): Promise<ContainerInfo|undefined> {
const containerId = await findRunningDockerContainerId();
if (!containerId)
return undefined;
const rawLogs = await callDockerAPI('get', `/containers/${containerId}/logs?stdout=true&stderr=true`).catch(e => '');
if (!rawLogs)
return undefined;
// Docker might prefix every log line with 8 characters. Stip them out.
// See https://github.com/moby/moby/issues/7375
// This doesn't happen if the containers is launched manually with attached terminal.
const logLines = rawLogs.split('\n').map(line => {
if ([0, 1, 2].includes(line.charCodeAt(0)))
return line.substring(8);
return line;
});
const WS_LINE_PREFIX = 'Listening on ws://';
const webSocketLine = logLines.find(line => line.startsWith(WS_LINE_PREFIX));
const NOVNC_LINE_PREFIX = 'novnc is listening on ';
const novncLine = logLines.find(line => line.startsWith(NOVNC_LINE_PREFIX));
return novncLine && webSocketLine ? {
wsEndpoint: 'ws://' + webSocketLine.substring(WS_LINE_PREFIX.length),
vncSession: novncLine.substring(NOVNC_LINE_PREFIX.length),
} : undefined;
}
export async function ensureContainerOrDie(): Promise<ContainerInfo> {
const pwImage = await findDockerImage(VRT_IMAGE_NAME);
if (!pwImage) {
console.error('\n' + utils.wrapInASCIIBox([
`Failed to find local docker image.`,
`Please build local docker image with the following command:`,
``,
` npx playwright docker build`,
``,
`<3 Playwright Team`,
].join('\n'), 1));
process.exit(1);
}
let info = await containerInfo();
if (info)
return info;
await launchContainer({
image: pwImage,
name: VRT_CONTAINER_NAME,
autoRemove: true,
ports: [5400, 7900],
});
// Wait for the service to become available.
const startTime = Date.now();
const timeouts = [0, 100, 100, 200, 500, 1000];
do {
await new Promise(x => setTimeout(x, timeouts.shift() ?? 1000));
info = await containerInfo();
} while (!info && Date.now() < startTime + 60000);
if (!info)
throw new Error('Failed to launch docker container!');
return info;
}
export async function stopContainer() {
const containerId = await findRunningDockerContainerId();
if (!containerId)
return;
await Promise.all([
// Make sure to wait for the container to be removed.
postJSON(`/containers/${containerId}/wait?condition=removed`),
postJSON(`/containers/${containerId}/kill`),
]);
}
export async function ensureDockerEngineIsRunningOrDie() {
try {
await callDockerAPI('get', '/info');
} catch (e) {
console.error(utils.wrapInASCIIBox([
`Docker is not running!`,
`Please install and launch docker:`,
``,
` https://docs.docker.com/get-docker`,
``,
].join('\n'), 1));
process.exit(1);
}
}
async function findDockerImage(imageName: string): Promise<DockerImage|undefined> {
const images: DockerImage[] | null = await getJSON('/images/json');
return images ? images.find(image => image.RepoTags?.includes(imageName)) : undefined;
}
interface Container {
ImageID: string;
State: string;
Names: [string];
Id: string;
}
async function findRunningDockerContainerId(): Promise<string|undefined> {
const containers: (Container[]|undefined) = await getJSON('/containers/json');
if (!containers)
return undefined;
const dockerImage = await findDockerImage(VRT_IMAGE_NAME);
const container = dockerImage ? containers.find((container: Container) => container.ImageID === dockerImage.Id) : undefined;
return container?.State === 'running' ? container.Id : undefined;
}
interface ContainerOptions {
image: DockerImage;
autoRemove: boolean;
command?: string[];
ports?: Number[];
name?: string;
}
async function launchContainer(options: ContainerOptions): Promise<string> {
const ExposedPorts: any = {};
const PortBindings: any = {};
for (const port of (options.ports ?? [])) {
ExposedPorts[`${port}/tcp`] = {};
PortBindings[`${port}/tcp`] = [{ HostPort: port + '' }];
}
const container = await postJSON(`/containers/create` + (options.name ? '?name=' + options.name : ''), {
Cmd: options.command,
AttachStdout: true,
AttachStderr: true,
Image: options.image.Id,
ExposedPorts,
HostConfig: {
Init: true,
AutoRemove: options.autoRemove,
ShmSize: 2 * 1024 * 1024 * 1024,
PortBindings,
},
});
await postJSON(`/containers/${container.Id}/start`);
return container.Id;
}
async function getJSON(url: string): Promise<any> {
const result = await callDockerAPI('get', url);
if (!result)
return result;
return JSON.parse(result);
}
async function postJSON(url: string, json: any = undefined) {
const result = await callDockerAPI('post', url, json ? JSON.stringify(json) : undefined);
if (!result)
return result;
return JSON.parse(result);
}
const DOCKER_API_VERSION = '1.41';
function callDockerAPI(method: 'post'|'get'|'delete', url: string, body: Buffer|string|undefined = undefined): Promise<string> {
const dockerSocket = process.platform === 'win32' ? '\\\\.\\pipe\\docker_engine' : '/var/run/docker.sock';
return new Promise((resolve, reject) => {
const request = http.request({
socketPath: dockerSocket,
path: `/v${DOCKER_API_VERSION}${url}`,
timeout: 30000,
method,
}, (response: http.IncomingMessage) => {
let body = '';
response.on('data', function(chunk){
body += chunk;
});
response.on('end', function(){
if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 300)
reject(new Error(`${method} ${url} FAILED with statusCode ${response.statusCode} and body\n${body}`));
else
resolve(body);
});
});
request.on('error', function(e){
reject(e);
});
if (body) {
request.setHeader('Content-Type', 'application/json');
request.setHeader('Content-Length', body.length);
request.write(body);
} else {
request.setHeader('Content-Type', 'text/plain');
}
request.end();
});
}

View file

@ -72,10 +72,7 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: 'worker', option: true }], headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: 'worker', option: true }],
channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: 'worker', option: true }], channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: 'worker', option: true }],
launchOptions: [{}, { scope: 'worker', option: true }], launchOptions: [{}, { scope: 'worker', option: true }],
connectOptions: [process.env.PW_TEST_CONNECT_WS_ENDPOINT ? { connectOptions: [process.env.PW_TEST_CONNECT_WS_ENDPOINT ? { wsEndpoint: process.env.PW_TEST_CONNECT_WS_ENDPOINT } : undefined, { scope: 'worker', option: true }],
wsEndpoint: process.env.PW_TEST_CONNECT_WS_ENDPOINT,
headers: process.env.PW_TEST_CONNECT_HEADERS ? JSON.parse(process.env.PW_TEST_CONNECT_HEADERS) : undefined,
} : undefined, { scope: 'worker', option: true }],
screenshot: ['off', { scope: 'worker', option: true }], screenshot: ['off', { scope: 'worker', option: true }],
video: ['off', { scope: 'worker', option: true }], video: ['off', { scope: 'worker', option: true }],
trace: ['off', { scope: 'worker', option: true }], trace: ['off', { scope: 'worker', option: true }],
@ -223,7 +220,7 @@ export const test = _baseTest.extend<TestFixtures, WorkerFixtures>({
}); });
}, },
_snapshotSuffix: [process.env.PW_TEST_SNAPSHOT_SUFFIX ?? process.platform, { scope: 'worker' }], _snapshotSuffix: [process.platform, { scope: 'worker' }],
_setupContextOptionsAndArtifacts: [async ({ playwright, _snapshotSuffix, _combinedContextOptions, _browserOptions, _artifactsDir, trace, screenshot, actionTimeout, navigationTimeout }, use, testInfo) => { _setupContextOptionsAndArtifacts: [async ({ playwright, _snapshotSuffix, _combinedContextOptions, _browserOptions, _artifactsDir, trace, screenshot, actionTimeout, navigationTimeout }, use, testInfo) => {
testInfo.snapshotSuffix = _snapshotSuffix; testInfo.snapshotSuffix = _snapshotSuffix;

View file

@ -295,8 +295,6 @@ export class Loader {
} }
private async _requireOrImport(file: string) { private async _requireOrImport(file: string) {
if (process.platform === 'win32')
file = await fixWin32FilepathCapitalization(file);
const revertBabelRequire = installTransform(); const revertBabelRequire = installTransform();
const isModule = fileIsModule(file); const isModule = fileIsModule(file);
try { try {
@ -688,23 +686,3 @@ export function folderIsModule(folder: string): boolean {
// Rely on `require` internal caching logic. // Rely on `require` internal caching logic.
return require(packageJsonPath).type === 'module'; return require(packageJsonPath).type === 'module';
} }
async function fixWin32FilepathCapitalization(file: string): Promise<string> {
/**
* On Windows with PowerShell <= 6 it is possible to have a CWD with different
* casing than what the actual directory on the filesystem is. This can cause
* that we require the file multiple times with different casing. To mitigate
* this we get the actual underlying filesystem path and use that.
* https://github.com/microsoft/playwright/issues/9193#issuecomment-1219362150
*/
const realFile = await new Promise<string>((resolve, reject) => fs.realpath.native(file, (error, realFile) => {
if (error)
return reject(error);
resolve(realFile);
}));
// We do not want to resolve them (e.g. 8.3 filenames), so we do a best effort
// approach by only using it if the actual lowercase characters are the same:
if (realFile.toLowerCase() === file.toLowerCase())
return realFile;
return file;
}

View file

@ -17,7 +17,7 @@
import type { Locator, Page, APIResponse } from 'playwright-core'; import type { Locator, Page, APIResponse } from 'playwright-core';
import type { FrameExpectOptions } from 'playwright-core/lib/client/types'; import type { FrameExpectOptions } from 'playwright-core/lib/client/types';
import { colors } from 'playwright-core/lib/utilsBundle'; import { colors } from 'playwright-core/lib/utilsBundle';
import { constructURLBasedOnBaseURL, isRegExp } from 'playwright-core/lib/utils'; import { constructURLBasedOnBaseURL } from 'playwright-core/lib/utils';
import type { Expect } from '../types'; import type { Expect } from '../types';
import { expectTypes, callLogText } from '../util'; import { expectTypes, callLogText } from '../util';
import { toBeTruthy } from './toBeTruthy'; import { toBeTruthy } from './toBeTruthy';
@ -141,25 +141,13 @@ export function toHaveAttribute(
this: ReturnType<Expect['getState']>, this: ReturnType<Expect['getState']>,
locator: LocatorEx, locator: LocatorEx,
name: string, name: string,
expected: string | RegExp | undefined | { timeout?: number}, expected: string | RegExp,
options?: { timeout?: number }, options?: { timeout?: number },
) { ) {
if (!options) {
// Update params for the case toHaveAttribute(name, options);
if (typeof expected === 'object' && !isRegExp(expected)) {
options = expected;
expected = undefined;
}
}
if (expected === undefined) {
return toBeTruthy.call(this, 'toHaveAttribute', locator, 'Locator', async (isNot, timeout, customStackTrace) => {
return await locator._expect(customStackTrace, 'to.have.attribute', { expressionArg: name, isNot, timeout });
}, options);
}
return toMatchText.call(this, 'toHaveAttribute', locator, 'Locator', async (isNot, timeout, customStackTrace) => { return toMatchText.call(this, 'toHaveAttribute', locator, 'Locator', async (isNot, timeout, customStackTrace) => {
const expectedText = toExpectedTextValues([expected as (string | RegExp)]); const expectedText = toExpectedTextValues([expected]);
return await locator._expect(customStackTrace, 'to.have.attribute.value', { expressionArg: name, expectedText, isNot, timeout }); return await locator._expect(customStackTrace, 'to.have.attribute', { expressionArg: name, expectedText, isNot, timeout });
}, expected as (string | RegExp), options); }, expected, options);
} }
export function toHaveClass( export function toHaveClass(

View file

@ -26,9 +26,10 @@ import { collectComponentUsages, componentInfo } from '../tsxTransform';
import type { FullConfig } from '../types'; import type { FullConfig } from '../types';
import { assert, calculateSha1 } from 'playwright-core/lib/utils'; import { assert, calculateSha1 } from 'playwright-core/lib/utils';
import type { AddressInfo } from 'net'; import type { AddressInfo } from 'net';
import { getPlaywrightVersion } from 'playwright-core/lib/common/userAgent';
let stoppableServer: any; let stoppableServer: any;
const VERSION = 6; const playwrightVersion = getPlaywrightVersion();
type CtConfig = { type CtConfig = {
ctPort?: number; ctPort?: number;
@ -65,14 +66,17 @@ export function createPlugin(
const registerSource = await fs.promises.readFile(registerSourceFile, 'utf-8'); const registerSource = await fs.promises.readFile(registerSourceFile, 'utf-8');
const registerSourceHash = calculateSha1(registerSource); const registerSourceHash = calculateSha1(registerSource);
const { version: viteVersion } = require('vite/package.json');
try { try {
buildInfo = JSON.parse(await fs.promises.readFile(buildInfoFile, 'utf-8')) as BuildInfo; buildInfo = JSON.parse(await fs.promises.readFile(buildInfoFile, 'utf-8')) as BuildInfo;
assert(buildInfo.version === VERSION); assert(buildInfo.version === playwrightVersion);
assert(buildInfo.viteVersion === viteVersion);
assert(buildInfo.registerSourceHash === registerSourceHash); assert(buildInfo.registerSourceHash === registerSourceHash);
buildExists = true; buildExists = true;
} catch (e) { } catch (e) {
buildInfo = { buildInfo = {
version: VERSION, version: playwrightVersion,
viteVersion,
registerSourceHash, registerSourceHash,
components: [], components: [],
tests: {}, tests: {},
@ -156,7 +160,8 @@ export function createPlugin(
} }
type BuildInfo = { type BuildInfo = {
version: number, version: string,
viteVersion: string,
registerSourceHash: string, registerSourceHash: string,
sources: { sources: {
[key: string]: { [key: string]: {

View file

@ -29,6 +29,7 @@ import { Loader } from './loader';
import type { FullResult, Reporter, TestError } from '../types/testReporter'; import type { FullResult, Reporter, TestError } from '../types/testReporter';
import { Multiplexer } from './reporters/multiplexer'; import { Multiplexer } from './reporters/multiplexer';
import { formatError } from './reporters/base'; import { formatError } from './reporters/base';
import { colors } from 'playwright-core/lib/utilsBundle';
import DotReporter from './reporters/dot'; import DotReporter from './reporters/dot';
import GitHubReporter from './reporters/github'; import GitHubReporter from './reporters/github';
import LineReporter from './reporters/line'; import LineReporter from './reporters/line';
@ -400,6 +401,15 @@ export class Runner {
if (result.status !== 'passed') if (result.status !== 'passed')
return result; return result;
if (config._ignoreSnapshots) {
this._reporter.onStdOut?.(colors.dim([
'NOTE: running with "ignoreSnapshots" option. All of the following asserts are silently ignored:',
'- expect().toMatchSnapshot()',
'- expect().toHaveScreenshot()',
'',
].join('\n')));
}
// 14. Run tests. // 14. Run tests.
try { try {
const sigintWatcher = new SigIntWatcher(); const sigintWatcher = new SigIntWatcher();

View file

@ -3437,11 +3437,10 @@ interface LocatorAssertions {
}): Promise<void>; }): Promise<void>;
/** /**
* Ensures the [Locator] points to an element with given attribute value. * Ensures the [Locator] points to an element with given attribute.
* *
* ```js * ```js
* const locator = page.locator('input'); * const locator = page.locator('input');
* // Assert attribute with given value.
* await expect(locator).toHaveAttribute('type', 'text'); * await expect(locator).toHaveAttribute('type', 'text');
* ``` * ```
* *
@ -3456,26 +3455,6 @@ interface LocatorAssertions {
timeout?: number; timeout?: number;
}): Promise<void>; }): Promise<void>;
/**
* Ensures the [Locator] points to an element with given attribute. The method will assert attribute presence.
*
* ```js
* const locator = page.locator('input');
* // Assert attribute existance.
* await expect(locator).toHaveAttribute('disabled');
* await expect(locator).not.toHaveAttribute('open');
* ```
*
* @param name Attribute name.
* @param options
*/
toHaveAttribute(name: string, options?: {
/**
* Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`.
*/
timeout?: number;
}): Promise<void>;
/** /**
* Ensures the [Locator] points to an element with given CSS classes. This needs to be a full match or using a relaxed * Ensures the [Locator] points to an element with given CSS classes. This needs to be a full match or using a relaxed
* regular expression. * regular expression.

View file

@ -1,6 +1,6 @@
{ {
"name": "playwright-webkit", "name": "playwright-webkit",
"version": "1.26.0-next", "version": "1.26.1",
"description": "A high-level API to automate WebKit", "description": "A high-level API to automate WebKit",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -28,6 +28,6 @@
"install": "node install.js" "install": "node install.js"
}, },
"dependencies": { "dependencies": {
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "playwright", "name": "playwright",
"version": "1.26.0-next", "version": "1.26.1",
"description": "A high-level API to automate web browsers", "description": "A high-level API to automate web browsers",
"repository": "github:Microsoft/playwright", "repository": "github:Microsoft/playwright",
"homepage": "https://playwright.dev", "homepage": "https://playwright.dev",
@ -28,6 +28,6 @@
"install": "node install.js" "install": "node install.js"
}, },
"dependencies": { "dependencies": {
"playwright-core": "1.26.0-next" "playwright-core": "1.26.1"
} }
} }

View file

@ -1,130 +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.
*/
import { test, expect } from './npmTest';
import * as path from 'path';
import * as fs from 'fs';
import { TestServer } from '../../utils/testserver';
// Skipping docker tests on CI on non-linux since GHA does not have
// Docker engine installed on macOS and Windows.
test.skip(() => process.env.CI && process.platform !== 'linux');
test.beforeAll(async ({ exec }) => {
// Delete any previous docker image to ensure clean run.
await exec('npx playwright docker delete-image', {
cwd: path.join(__dirname, '..', '..'),
});
});
test('make sure it tells to run `npx playwright docker build` when image is not instaleld', async ({ exec }) => {
await exec('npm i --foreground-scripts @playwright/test');
const result = await exec('npx playwright docker test docker.spec.js', {
expectToExitWithError: true,
});
expect(result).toContain('npx playwright docker build');
});
test.describe('installed image', () => {
test.beforeAll(async ({ exec }) => {
await exec('npx playwright docker build', {
env: { PWTEST_DOCKER_BASE_IMAGE: 'playwright:installation-tests-focal' },
cwd: path.join(__dirname, '..', '..'),
});
});
test.afterAll(async ({ exec }) => {
await exec('npx playwright docker delete-image', {
cwd: path.join(__dirname, '..', '..'),
});
});
test('make sure it auto-starts container', async ({ exec }) => {
await exec('npm i --foreground-scripts @playwright/test');
await exec('npx playwright docker stop');
const result = await exec('npx playwright docker test docker.spec.js --grep platform');
expect(result).toContain('@chromium Linux');
});
test.describe('running container', () => {
test.beforeAll(async ({ exec }) => {
await exec('npx playwright docker start', {
cwd: path.join(__dirname, '..', '..'),
});
});
test.afterAll(async ({ exec }) => {
await exec('npx playwright docker stop', {
cwd: path.join(__dirname, '..', '..'),
});
});
test('all browsers work headless', async ({ exec }) => {
await exec('npm i --foreground-scripts @playwright/test');
const result = await exec('npx playwright docker test docker.spec.js --grep platform --browser all');
expect(result).toContain('@chromium Linux');
expect(result).toContain('@webkit Linux');
expect(result).toContain('@firefox Linux');
});
test('all browsers work headed', async ({ exec }) => {
await exec('npm i --foreground-scripts @playwright/test');
{
const result = await exec(`npx playwright docker test docker.spec.js --headed --grep userAgent --browser chromium`);
expect(result).toContain('@chromium');
expect(result).not.toContain('Headless');
expect(result).toContain(' Chrome/');
}
{
const result = await exec(`npx playwright docker test docker.spec.js --headed --grep userAgent --browser webkit`);
expect(result).toContain('@webkit');
expect(result).toContain(' Version/');
}
{
const result = await exec(`npx playwright docker test docker.spec.js --headed --grep userAgent --browser firefox`);
expect(result).toContain('@firefox');
expect(result).toContain(' Firefox/');
}
});
test('screenshots have docker suffix', async ({ exec, tmpWorkspace }) => {
await exec('npm i --foreground-scripts @playwright/test');
await exec('npx playwright docker test docker.spec.js --grep screenshot --browser all', {
expectToExitWithError: true,
});
const files = await fs.promises.readdir(path.join(tmpWorkspace, 'docker.spec.js-snapshots'));
expect(files).toContain('img-chromium-docker.png');
expect(files).toContain('img-firefox-docker.png');
expect(files).toContain('img-webkit-docker.png');
});
test('port forwarding works', async ({ exec, tmpWorkspace }) => {
await exec('npm i --foreground-scripts @playwright/test');
const TEST_PORT = 8425;
const server = await TestServer.create(tmpWorkspace, TEST_PORT);
server.setRoute('/', (request, response) => {
response.end('Hello from host');
});
const result = await exec('npx playwright docker test docker.spec.js --grep localhost --browser all', {
env: {
TEST_PORT: TEST_PORT + '',
},
});
expect(result).toContain('@chromium Hello from host');
expect(result).toContain('@webkit Hello from host');
expect(result).toContain('@firefox Hello from host');
});
});
});

View file

@ -1,19 +0,0 @@
const { test, expect } = require('@playwright/test');
test('platform', async ({ page }) => {
console.log('@' + page.context().browser().browserType().name(), await page.evaluate(() => navigator.platform));
});
test('userAgent', async ({ page }) => {
console.log('@' + page.context().browser().browserType().name(), await page.evaluate(() => navigator.userAgent));
});
test('screenshot', async ({ page }) => {
await expect(page).toHaveScreenshot('img.png');
});
test('localhost', async ({ page }) => {
expect(process.env.TEST_PORT).toBeTruthy();
await page.goto('http://localhost:' + process.env.TEST_PORT);
console.log('@' + page.context().browser().browserType().name(), await page.textContent('body'));
});

View file

@ -21,55 +21,40 @@ import fs from 'fs';
import { TMP_WORKSPACES } from './npmTest'; import { TMP_WORKSPACES } from './npmTest';
const PACKAGE_BUILDER_SCRIPT = path.join(__dirname, '..', '..', 'utils', 'pack_package.js'); const PACKAGE_BUILDER_SCRIPT = path.join(__dirname, '..', '..', 'utils', 'pack_package.js');
const DOCKER_BUILDER_SCRIPT = path.join(__dirname, '..', '..', 'utils', 'docker', 'build.sh');
async function globalSetup() { async function globalSetup() {
await promisify(rimraf)(TMP_WORKSPACES); await promisify(rimraf)(TMP_WORKSPACES);
console.log(`Temporary workspaces will be created in ${TMP_WORKSPACES}. They will not be removed at the end. Set DEBUG=itest to determine which sub-dir a specific test is using.`); console.log(`Temporary workspaces will be created in ${TMP_WORKSPACES}. They will not be removed at the end. Set DEBUG=itest to determine which sub-dir a specific test is using.`);
await fs.promises.mkdir(TMP_WORKSPACES, { recursive: true }); await fs.promises.mkdir(TMP_WORKSPACES, { recursive: true });
if (process.env.PWTEST_INSTALLATION_TEST_SKIP_PACKAGE_BUILDS) { if (process.env.PWTEST_INSTALLATION_TEST_SKIP_PACKAGE_BUILDS) {
console.log('Skipped building packages. Unset PWTEST_INSTALLATION_TEST_SKIP_PACKAGE_BUILDS to build packages.'); console.log('Skipped building packages. Unset PWTEST_INSTALLATION_TEST_SKIP_PACKAGE_BUILDS to build packages.');
} else { return;
console.log('Building packages. Set PWTEST_INSTALLATION_TEST_SKIP_PACKAGE_BUILDS to skip.');
const outputDir = path.join(__dirname, 'output');
await promisify(rimraf)(outputDir);
await fs.promises.mkdir(outputDir, { recursive: true });
const build = async (buildTarget: string, pkgNameOverride?: string) => {
const outPath = path.resolve(path.join(outputDir, `${buildTarget}.tgz`));
const { code, stderr, stdout } = await spawnAsync('node', [PACKAGE_BUILDER_SCRIPT, buildTarget, outPath]);
if (!!code)
throw new Error(`Failed to build: ${buildTarget}:\n${stderr}\n${stdout}`);
console.log('Built:', pkgNameOverride || buildTarget);
return [pkgNameOverride || buildTarget, outPath];
};
const builds = await Promise.all([
build('playwright-core'),
build('playwright-test', '@playwright/test'),
build('playwright'),
build('playwright-chromium'),
build('playwright-firefox'),
build('playwright-webkit'),
]);
await fs.promises.writeFile(path.join(__dirname, '.registry.json'), JSON.stringify(Object.fromEntries(builds)));
} }
if (process.env.CI && process.platform !== 'linux') { console.log('Building packages. Set PWTEST_INSTALLATION_TEST_SKIP_PACKAGE_BUILDS to skip.');
console.log('Skipped building docker: docker tests are not supported on Windows and macOS Github Actions.'); const outputDir = path.join(__dirname, 'output');
} else if (process.env.PWTEST_INSTALLATION_TEST_SKIP_DOCKER_BUILD) { await promisify(rimraf)(outputDir);
console.log('Skipped building docker. Unset PWTEST_INSTALLATION_TEST_SKIP_DOCKER_BUILD to build docker.'); await fs.promises.mkdir(outputDir, { recursive: true });
} else {
console.log('Building docker. Set PWTEST_INSTALLATION_TEST_SKIP_DOCKER_BUILD to skip.'); const build = async (buildTarget: string, pkgNameOverride?: string) => {
const DOCKER_IMAGE_NAME = 'playwright:installation-tests-focal'; const outPath = path.resolve(path.join(outputDir, `${buildTarget}.tgz`));
const arch = process.arch === 'arm64' ? '--arm64' : '--amd64'; const { code, stderr, stdout } = await spawnAsync('node', [PACKAGE_BUILDER_SCRIPT, buildTarget, outPath]);
const { code, stderr, stdout } = await spawnAsync('bash', [DOCKER_BUILDER_SCRIPT, arch, 'focal', DOCKER_IMAGE_NAME]);
if (!!code) if (!!code)
throw new Error(`Failed to build docker:\n${stderr}\n${stdout}`); throw new Error(`Failed to build: ${buildTarget}:\n${stderr}\n${stdout}`);
console.log('Built: docker image ', DOCKER_IMAGE_NAME); console.log('Built:', pkgNameOverride || buildTarget);
} return [pkgNameOverride || buildTarget, outPath];
};
const builds = await Promise.all([
build('playwright-core'),
build('playwright-test', '@playwright/test'),
build('playwright'),
build('playwright-chromium'),
build('playwright-firefox'),
build('playwright-webkit'),
]);
await fs.promises.writeFile(path.join(__dirname, '.registry.json'), JSON.stringify(Object.fromEntries(builds)));
} }
export default globalSetup; export default globalSetup;

View file

@ -25,8 +25,8 @@ import debugLogger from 'debug';
import { Registry } from './registry'; import { Registry } from './registry';
import { spawnAsync } from './spawnAsync'; import { spawnAsync } from './spawnAsync';
// os.tmpdir() on Windows returns a 8.3 filename, so we resolve it.
export const TMP_WORKSPACES = path.join(os.platform() === 'darwin' ? '/tmp' : fs.realpathSync.native(os.tmpdir()), 'pwt', 'workspaces'); export const TMP_WORKSPACES = path.join(os.platform() === 'darwin' ? '/tmp' : os.tmpdir(), 'pwt', 'workspaces');
const debug = debugLogger('itest'); const debug = debugLogger('itest');

View file

@ -1,41 +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.
*/
import fs from 'fs';
import path from 'path';
import { test, expect } from './npmTest';
test('@playwright/test should handle incorrect cwd casing', async ({ exec, tmpWorkspace }) => {
test.skip(process.platform !== 'win32');
const cwd = path.join(tmpWorkspace, 'expectedcasing');
fs.mkdirSync(cwd);
fs.writeFileSync(path.join(cwd, 'sample.spec.ts'), `
import { test, expect } from '@playwright/test';
test('should pass', async () => {
expect(1 + 1).toBe(2);
})
`);
fs.writeFileSync(path.join(cwd, 'sample.spec.js'), `
const { test, expect } = require('@playwright/test');
test('should pass', async () => {
expect(1 + 1).toBe(2);
})
`);
await exec('npm init -y', { cwd });
await exec('npm i --foreground-scripts @playwright/test', { cwd });
const output = await exec('npx playwright test --reporter=list', { cwd: path.join(tmpWorkspace, 'eXpEcTeDcAsInG') });
expect(output).toContain('2 passed');
});

View file

@ -27,7 +27,7 @@ const config: PlaywrightTestConfig = {
timeout: 5 * 60 * 1000, timeout: 5 * 60 * 1000,
retries: 0, retries: 0,
reporter: process.env.CI ? [ reporter: process.env.CI ? [
['list'], ['dot'],
['json', { outputFile: path.join(outputDir, 'report.json') }], ['json', { outputFile: path.join(outputDir, 'report.json') }],
] : [['list'], ['html', { open: 'on-failure' }]], ] : [['list'], ['html', { open: 'on-failure' }]],
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,

View file

@ -232,7 +232,7 @@ public class Tests : PageTest
}; };
} }
[PlaywrightTestMethod] [TestMethod]
public async Task MyTest() public async Task MyTest()
{ {
// Go to ${emptyHTML} // Go to ${emptyHTML}
@ -261,7 +261,7 @@ public class Tests : PageTest
}; };
} }
[PlaywrightTest] [Test]
public async Task MyTest() public async Task MyTest()
{ {
// Go to ${emptyHTML} // Go to ${emptyHTML}

View file

@ -228,22 +228,10 @@ test.describe('toHaveURL', () => {
test.describe('toHaveAttribute', () => { test.describe('toHaveAttribute', () => {
test('pass', async ({ page }) => { test('pass', async ({ page }) => {
await page.setContent('<div checked id=node>Text content</div>'); await page.setContent('<div id=node>Text content</div>');
const locator = page.locator('#node'); const locator = page.locator('#node');
await expect(locator).toHaveAttribute('id');
await expect(locator).toHaveAttribute('checked');
await expect(locator).not.toHaveAttribute('open');
await expect(locator).toHaveAttribute('id', 'node'); await expect(locator).toHaveAttribute('id', 'node');
}); });
test('should support boolean attribute with options', async ({ page }) => {
await page.setContent('<div checked id=node>Text content</div>');
const locator = page.locator('#node');
await expect(locator).toHaveAttribute('id', { timeout: 5000 });
await expect(locator).toHaveAttribute('checked', { timeout: 5000 });
await expect(locator).not.toHaveAttribute('open', { timeout: 5000 });
await expect(locator).toHaveAttribute('id', 'node', { timeout: 5000 });
});
}); });
test.describe('toHaveCSS', () => { test.describe('toHaveCSS', () => {

View file

@ -299,26 +299,6 @@ test('should return void/Promise when appropriate', async ({ runTSC }) => {
expect(result.exitCode).toBe(0); expect(result.exitCode).toBe(0);
}); });
test('should suppport toHaveAttribute withou optional value', async ({ runTSC }) => {
const result = await runTSC({
'a.spec.ts': `
const { test } = pwt;
test('custom matchers', async ({ page }) => {
const locator = page.locator('#node');
await test.expect(locator).toHaveAttribute('name', 'value');
await test.expect(locator).toHaveAttribute('name', 'value', { timeout: 10 });
await test.expect(locator).toHaveAttribute('disabled');
await test.expect(locator).toHaveAttribute('disabled', { timeout: 10 });
// @ts-expect-error
await test.expect(locator).toHaveAttribute('disabled', { foo: 1 });
// @ts-expect-error
await test.expect(locator).toHaveAttribute('name', 'value', 'opt');
});
`
});
expect(result.exitCode).toBe(0);
});
test.describe('helpful expect errors', () => { test.describe('helpful expect errors', () => {
test('top-level', async ({ runInlineTest }) => { test('top-level', async ({ runInlineTest }) => {
const result = await runInlineTest({ const result = await runInlineTest({

View file

@ -37,7 +37,8 @@ test('should work with the empty component list', async ({ runInlineTest }, test
expect(output.replace(/\\+/g, '/')).toContain('playwright/.cache/playwright/index.html'); expect(output.replace(/\\+/g, '/')).toContain('playwright/.cache/playwright/index.html');
const metainfo = JSON.parse(fs.readFileSync(testInfo.outputPath('playwright/.cache/metainfo.json'), 'utf-8')); const metainfo = JSON.parse(fs.readFileSync(testInfo.outputPath('playwright/.cache/metainfo.json'), 'utf-8'));
expect(metainfo.version).toEqual(expect.any(Number)); expect(metainfo.version).toEqual(require('playwright-core/package.json').version);
expect(metainfo.viteVersion).toEqual(require('vite/package.json').version);
expect(Object.entries(metainfo.tests)).toHaveLength(1); expect(Object.entries(metainfo.tests)).toHaveLength(1);
expect(Object.entries(metainfo.sources)).toHaveLength(8); expect(Object.entries(metainfo.sources)).toHaveLength(8);
}); });

View file

@ -308,7 +308,7 @@ copyFiles.push({
}); });
copyFiles.push({ copyFiles.push({
files: 'packages/playwright-test/src/**/*.(js|sh)', files: 'packages/playwright-test/src/**/*.js',
from: 'packages/playwright-test/src', from: 'packages/playwright-test/src',
to: 'packages/playwright-test/lib', to: 'packages/playwright-test/lib',
ignored: ['**/.eslintrc.js'] ignored: ['**/.eslintrc.js']

View file

@ -1,4 +1,6 @@
@ECHO OFF @echo off
SETLOCAL setlocal
IF %PLAYWRIGHT_NODEJS_PATH%x == x SET PLAYWRIGHT_NODEJS_PATH="%~dp0\node.exe" if not defined PLAYWRIGHT_NODEJS_PATH (
"%PLAYWRIGHT_NODEJS_PATH%" "%~dp0\package\lib\cli\cli.js" %* set PLAYWRIGHT_NODEJS_PATH=%~dp0\node.exe
)
"""%PLAYWRIGHT_NODEJS_PATH%""" "%~dp0\package\lib\cli\cli.js" %*

View file

@ -27,9 +27,7 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
# The package should be built beforehand from tip-of-tree Playwright. # The package should be built beforehand from tip-of-tree Playwright.
COPY ./playwright-core.tar.gz /tmp/playwright-core.tar.gz COPY ./playwright-core.tar.gz /tmp/playwright-core.tar.gz
# 2. Bake in Playwright Agent. # 2. Bake in browsers & deps.
# Playwright Agent is used to bake in browsers and browser dependencies,
# and run docker server later on.
# Browsers will be downloaded in `/ms-playwright`. # Browsers will be downloaded in `/ms-playwright`.
# Note: make sure to set 777 to the registry so that any user can access # Note: make sure to set 777 to the registry so that any user can access
# registry. # registry.
@ -40,4 +38,5 @@ RUN mkdir /ms-playwright && \
npx playwright mark-docker-image "${DOCKER_IMAGE_NAME_TEMPLATE}" && \ npx playwright mark-docker-image "${DOCKER_IMAGE_NAME_TEMPLATE}" && \
npx playwright install --with-deps && rm -rf /var/lib/apt/lists/* && \ npx playwright install --with-deps && rm -rf /var/lib/apt/lists/* && \
rm /tmp/playwright-core.tar.gz && \ rm /tmp/playwright-core.tar.gz && \
rm -rf /ms-playwright-agent && \
chmod -R 777 /ms-playwright chmod -R 777 /ms-playwright

View file

@ -226,11 +226,6 @@ for (const [name, type] of modelTypes)
for (const [name, literals] of enumTypes) for (const [name, literals] of enumTypes)
renderEnum(name, literals); renderEnum(name, literals);
if (process.argv[3] !== '--skip-format') {
// run the formatting tool for .NET, to ensure the files are prepped
execSync(`dotnet format "${outputDir}"`);
}
/** /**
* @param {string} name * @param {string} name
*/ */

View file

@ -37,9 +37,8 @@ using System.Threading.Tasks;
#nullable enable #nullable enable
namespace Microsoft.Playwright namespace Microsoft.Playwright;
{
[CONTENT] [CONTENT]
}
#nullable disable #nullable disable

View file

@ -183,14 +183,14 @@ fs.mkdirSync(dir, { recursive: true });
for (const [name, item] of Object.entries(protocol)) { for (const [name, item] of Object.entries(protocol)) {
if (item.type === 'interface') { if (item.type === 'interface') {
const init = objectType(item.initializer || {}, ' '); const init = objectType(item.initializer || {}, '');
const initializerName = name + 'Initializer'; const initializerName = name + 'Initializer';
const superName = inherits.has(name) ? inherits.get(name) + 'Initializer' : null; const superName = inherits.has(name) ? inherits.get(name) + 'Initializer' : null;
writeCSharpClass(initializerName, superName, init.ts); writeCSharpClass(initializerName, superName, init.ts);
} else if (item.type === 'object') { } else if (item.type === 'object') {
if (Object.keys(item.properties).length === 0) if (Object.keys(item.properties).length === 0)
continue; continue;
const init = objectType(item.properties, ' ', false, name); const init = objectType(item.properties, '', false, name);
writeCSharpClass(name, null, init.ts); writeCSharpClass(name, null, init.ts);
} }
} }
@ -209,11 +209,10 @@ function writeCSharpClass(className, inheritFrom, serializedProperties) {
channels_ts.push('using System.Collections.Generic;'); channels_ts.push('using System.Collections.Generic;');
channels_ts.push('using System.Text.Json.Serialization;'); channels_ts.push('using System.Text.Json.Serialization;');
channels_ts.push(``); channels_ts.push(``);
channels_ts.push(`namespace Microsoft.Playwright.Transport.Protocol`); channels_ts.push(`namespace Microsoft.Playwright.Transport.Protocol;`);
channels_ts.push(`{`); channels_ts.push(``);
channels_ts.push(` internal class ${className}${inheritFrom ? ' : ' + inheritFrom : ''}`); channels_ts.push(`internal class ${className}${inheritFrom ? ' : ' + inheritFrom : ''}`);
channels_ts.push(serializedProperties); channels_ts.push(serializedProperties);
channels_ts.push(`}`);
channels_ts.push(``); channels_ts.push(``);
writeFile(`${className}.cs`, channels_ts.join('\n')); writeFile(`${className}.cs`, channels_ts.join('\n'));
} }