mirror of
https://github.com/microsoft/playwright-java.git
synced 2025-09-08 21:01:00 +00:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
eb1fea9907 | ||
|
dd99ce8b34 | ||
|
ed8e9c434f | ||
|
aee298b293 | ||
|
fd2ab4708a | ||
|
2a6cdff664 | ||
|
44161e0558 | ||
|
954b1c43ef | ||
|
f4c7b9734f | ||
|
dd87b300fb | ||
|
f83c03af68 | ||
|
d26dd0b112 |
4
.github/workflows/publish_docker.yml
vendored
4
.github/workflows/publish_docker.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
environment: Docker
|
environment: Docker
|
||||||
if: github.repository == 'microsoft/playwright-java'
|
if: github.repository == 'microsoft/playwright-java'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Azure login
|
- name: Azure login
|
||||||
uses: azure/login@v2
|
uses: azure/login@v2
|
||||||
with:
|
with:
|
||||||
@ -26,5 +26,5 @@ jobs:
|
|||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
with:
|
||||||
platforms: arm64
|
platforms: arm64
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- run: ./utils/docker/publish_docker.sh stable
|
- run: ./utils/docker/publish_docker.sh stable
|
||||||
|
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
@ -20,9 +20,9 @@ jobs:
|
|||||||
browser: [chromium, firefox, webkit]
|
browser: [chromium, firefox, webkit]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Set up JDK 1.8
|
- name: Set up JDK 1.8
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: zulu
|
distribution: zulu
|
||||||
java-version: 8
|
java-version: 8
|
||||||
@ -65,13 +65,13 @@ jobs:
|
|||||||
browser-channel: msedge
|
browser-channel: msedge
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Install Media Pack
|
- name: Install Media Pack
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
shell: powershell
|
shell: powershell
|
||||||
run: Install-WindowsFeature Server-Media-Foundation
|
run: Install-WindowsFeature Server-Media-Foundation
|
||||||
- name: Set up JDK 1.8
|
- name: Set up JDK 1.8
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: zulu
|
distribution: zulu
|
||||||
java-version: 8
|
java-version: 8
|
||||||
@ -100,9 +100,9 @@ jobs:
|
|||||||
browser: [chromium, firefox, webkit]
|
browser: [chromium, firefox, webkit]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Set up JDK 21
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
distribution: adopt
|
distribution: adopt
|
||||||
java-version: 21
|
java-version: 21
|
||||||
|
2
.github/workflows/test_cli.yml
vendored
2
.github/workflows/test_cli.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Cache Maven packages
|
- name: Cache Maven packages
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
|
22
.github/workflows/test_docker.yml
vendored
22
.github/workflows/test_docker.yml
vendored
@ -21,18 +21,32 @@ jobs:
|
|||||||
name: Test
|
name: Test
|
||||||
timeout-minutes: 120
|
timeout-minutes: 120
|
||||||
runs-on: ${{ matrix.runs-on }}
|
runs-on: ${{ matrix.runs-on }}
|
||||||
|
env:
|
||||||
|
PW_MAX_RETRIES: 3
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
flavor: [jammy, noble]
|
flavor: [jammy, noble]
|
||||||
runs-on: [ubuntu-24.04, ubuntu-24.04-arm]
|
runs-on: [ubuntu-24.04, ubuntu-24.04-arm]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Build Docker image
|
- name: Build Docker image
|
||||||
run: |
|
run: |
|
||||||
ARCH="${{ matrix.runs-on == 'ubuntu-24.04-arm' && 'arm64' || 'amd64' }}"
|
ARCH="${{ matrix.runs-on == 'ubuntu-24.04-arm' && 'arm64' || 'amd64' }}"
|
||||||
bash utils/docker/build.sh --$ARCH ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
|
bash utils/docker/build.sh --$ARCH ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
|
||||||
- name: Test
|
- name: Start container
|
||||||
run: |
|
run: |
|
||||||
CONTAINER_ID="$(docker run --rm -e CI --ipc=host -v $(pwd):/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)"
|
CONTAINER_ID=$(docker run --rm -e CI -e PW_MAX_RETRIES --ipc=host -v "$(pwd)":/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)
|
||||||
docker exec "${CONTAINER_ID}" /root/playwright/tools/test-local-installation/create_project_and_run_tests.sh
|
echo "CONTAINER_ID=$CONTAINER_ID" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Run test in container
|
||||||
|
run: |
|
||||||
|
docker exec "$CONTAINER_ID" /root/playwright/tools/test-local-installation/create_project_and_run_tests.sh
|
||||||
|
|
||||||
|
- name: Test ClassLoader
|
||||||
|
run: |
|
||||||
|
docker exec "${CONTAINER_ID}" /root/playwright/tools/test-spring-boot-starter/package_and_run_async_test.sh
|
||||||
|
|
||||||
|
- name: Stop container
|
||||||
|
run: |
|
||||||
|
docker stop "$CONTAINER_ID"
|
||||||
|
2
.github/workflows/verify_api.yml
vendored
2
.github/workflows/verify_api.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Download drivers
|
- name: Download drivers
|
||||||
run: scripts/download_driver.sh
|
run: scripts/download_driver.sh
|
||||||
- name: Regenerate APIs
|
- name: Regenerate APIs
|
||||||
|
@ -32,9 +32,9 @@ scripts/download_driver.sh
|
|||||||
mvn compile
|
mvn compile
|
||||||
mvn test
|
mvn test
|
||||||
# Executing a single test
|
# Executing a single test
|
||||||
BROWSER=chromium mvn test --projects=playwright -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize
|
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize
|
||||||
# Executing a single test class
|
# Executing a single test class
|
||||||
BROWSER=chromium mvn test --projects=playwright -Dtest=TestPageNetworkSizes
|
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes
|
||||||
```
|
```
|
||||||
|
|
||||||
### Generating API
|
### Generating API
|
||||||
|
@ -10,9 +10,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
|
|||||||
|
|
||||||
| | Linux | macOS | Windows |
|
| | Linux | macOS | Windows |
|
||||||
| :--- | :---: | :---: | :---: |
|
| :--- | :---: | :---: | :---: |
|
||||||
| Chromium <!-- GEN:chromium-version -->139.0.7258.5<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
| Chromium <!-- GEN:chromium-version -->140.0.7339.16<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||||
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||||
| Firefox <!-- GEN:firefox-version -->140.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
| Firefox <!-- GEN:firefox-version -->141.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ public class DriverJar extends Driver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static URI getDriverResourceURI() throws URISyntaxException {
|
public static URI getDriverResourceURI() throws URISyntaxException {
|
||||||
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
|
ClassLoader classloader = DriverJar.class.getClassLoader();
|
||||||
return classloader.getResource("driver/" + platformDir()).toURI();
|
return classloader.getResource("driver/" + platformDir()).toURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<name>Playwright Client Examples</name>
|
<name>Playwright Client Examples</name>
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<playwright.version>1.54.0</playwright.version>
|
<playwright.version>1.55.0</playwright.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -51,6 +51,10 @@ public interface APIRequest {
|
|||||||
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
||||||
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
||||||
*
|
*
|
||||||
|
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
|
||||||
|
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
|
||||||
|
* not match any of the domains you plan to visit.
|
||||||
|
*
|
||||||
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
||||||
* replacing {@code localhost} with {@code local.playwright}.
|
* replacing {@code localhost} with {@code local.playwright}.
|
||||||
*/
|
*/
|
||||||
@ -134,6 +138,10 @@ public interface APIRequest {
|
|||||||
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
||||||
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
||||||
*
|
*
|
||||||
|
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
|
||||||
|
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
|
||||||
|
* not match any of the domains you plan to visit.
|
||||||
|
*
|
||||||
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
||||||
* replacing {@code localhost} with {@code local.playwright}.
|
* replacing {@code localhost} with {@code local.playwright}.
|
||||||
*/
|
*/
|
||||||
|
@ -106,6 +106,10 @@ public interface Browser extends AutoCloseable {
|
|||||||
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
||||||
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
||||||
*
|
*
|
||||||
|
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
|
||||||
|
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
|
||||||
|
* not match any of the domains you plan to visit.
|
||||||
|
*
|
||||||
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
||||||
* replacing {@code localhost} with {@code local.playwright}.
|
* replacing {@code localhost} with {@code local.playwright}.
|
||||||
*/
|
*/
|
||||||
@ -323,6 +327,10 @@ public interface Browser extends AutoCloseable {
|
|||||||
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
||||||
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
||||||
*
|
*
|
||||||
|
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
|
||||||
|
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
|
||||||
|
* not match any of the domains you plan to visit.
|
||||||
|
*
|
||||||
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
||||||
* replacing {@code localhost} with {@code local.playwright}.
|
* replacing {@code localhost} with {@code local.playwright}.
|
||||||
*/
|
*/
|
||||||
@ -674,6 +682,10 @@ public interface Browser extends AutoCloseable {
|
|||||||
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
||||||
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
||||||
*
|
*
|
||||||
|
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
|
||||||
|
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
|
||||||
|
* not match any of the domains you plan to visit.
|
||||||
|
*
|
||||||
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
||||||
* replacing {@code localhost} with {@code local.playwright}.
|
* replacing {@code localhost} with {@code local.playwright}.
|
||||||
*/
|
*/
|
||||||
@ -891,6 +903,10 @@ public interface Browser extends AutoCloseable {
|
|||||||
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
||||||
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
||||||
*
|
*
|
||||||
|
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
|
||||||
|
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
|
||||||
|
* not match any of the domains you plan to visit.
|
||||||
|
*
|
||||||
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
||||||
* replacing {@code localhost} with {@code local.playwright}.
|
* replacing {@code localhost} with {@code local.playwright}.
|
||||||
*/
|
*/
|
||||||
|
@ -491,6 +491,10 @@ public interface BrowserType {
|
|||||||
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
||||||
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
||||||
*
|
*
|
||||||
|
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
|
||||||
|
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
|
||||||
|
* not match any of the domains you plan to visit.
|
||||||
|
*
|
||||||
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
||||||
* replacing {@code localhost} with {@code local.playwright}.
|
* replacing {@code localhost} with {@code local.playwright}.
|
||||||
*/
|
*/
|
||||||
@ -813,6 +817,10 @@ public interface BrowserType {
|
|||||||
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
|
||||||
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
|
||||||
*
|
*
|
||||||
|
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
|
||||||
|
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
|
||||||
|
* not match any of the domains you plan to visit.
|
||||||
|
*
|
||||||
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
|
||||||
* replacing {@code localhost} with {@code local.playwright}.
|
* replacing {@code localhost} with {@code local.playwright}.
|
||||||
*/
|
*/
|
||||||
@ -1386,6 +1394,11 @@ public interface BrowserType {
|
|||||||
* the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
|
* the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
|
||||||
*
|
*
|
||||||
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
|
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
|
||||||
|
*
|
||||||
|
* <p> <strong>NOTE:</strong> Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome user profile is not supported.
|
||||||
|
* Pointing {@code userDataDir} to Chrome's main "User Data" directory (the profile used for your regular browsing) may
|
||||||
|
* result in pages not loading or the browser exiting. Create and use a separate directory (for example, an empty folder)
|
||||||
|
* as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port for details.
|
||||||
* @since v1.8
|
* @since v1.8
|
||||||
*/
|
*/
|
||||||
default BrowserContext launchPersistentContext(Path userDataDir) {
|
default BrowserContext launchPersistentContext(Path userDataDir) {
|
||||||
@ -1406,6 +1419,11 @@ public interface BrowserType {
|
|||||||
* the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
|
* the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
|
||||||
*
|
*
|
||||||
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
|
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
|
||||||
|
*
|
||||||
|
* <p> <strong>NOTE:</strong> Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome user profile is not supported.
|
||||||
|
* Pointing {@code userDataDir} to Chrome's main "User Data" directory (the profile used for your regular browsing) may
|
||||||
|
* result in pages not loading or the browser exiting. Create and use a separate directory (for example, an empty folder)
|
||||||
|
* as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port for details.
|
||||||
* @since v1.8
|
* @since v1.8
|
||||||
*/
|
*/
|
||||||
BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options);
|
BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options);
|
||||||
|
@ -5812,8 +5812,8 @@ public interface Page extends AutoCloseable {
|
|||||||
*/
|
*/
|
||||||
Page opener();
|
Page opener();
|
||||||
/**
|
/**
|
||||||
* Pauses script execution. Playwright will stop executing the script and wait for the user to either press 'Resume' button
|
* Pauses script execution. Playwright will stop executing the script and wait for the user to either press the 'Resume'
|
||||||
* in the page overlay or to call {@code playwright.resume()} in the DevTools console.
|
* button in the page overlay or to call {@code playwright.resume()} in the DevTools console.
|
||||||
*
|
*
|
||||||
* <p> User can inspect selectors or perform manual steps while paused. Resume will continue running the original script from
|
* <p> User can inspect selectors or perform manual steps while paused. Resume will continue running the original script from
|
||||||
* the place it was paused.
|
* the place it was paused.
|
||||||
|
@ -1008,15 +1008,16 @@ public class PageImpl extends ChannelOwner implements Page {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pause() {
|
public void pause() {
|
||||||
Double defaultNavigationTimeout = browserContext.timeoutSettings.defaultNavigationTimeout();
|
TimeoutSettings settings = browserContext.timeoutSettings;
|
||||||
Double defaultTimeout = browserContext.timeoutSettings.defaultTimeout();
|
Double defaultNavigationTimeout = settings.defaultNavigationTimeout();
|
||||||
browserContext.setDefaultNavigationTimeout(0.0);
|
Double defaultTimeout = settings.defaultTimeout();
|
||||||
browserContext.setDefaultTimeout(0.0);
|
settings.setDefaultNavigationTimeout(0.0);
|
||||||
|
settings.setDefaultTimeout(0.0);
|
||||||
try {
|
try {
|
||||||
runUntil(() -> {}, new WaitableRace<>(asList(context().pause(), (Waitable<JsonElement>) waitableClosedOrCrashed)));
|
runUntil(() -> {}, new WaitableRace<>(asList(context().pause(), (Waitable<JsonElement>) waitableClosedOrCrashed)));
|
||||||
} finally {
|
} finally {
|
||||||
browserContext.setDefaultNavigationTimeout(defaultNavigationTimeout);
|
settings.setDefaultNavigationTimeout(defaultNavigationTimeout);
|
||||||
browserContext.setDefaultTimeout(defaultTimeout);
|
settings.setDefaultTimeout(defaultTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1166,18 +1167,8 @@ public class PageImpl extends ChannelOwner implements Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<Locator> mask = options.mask;
|
|
||||||
options.mask = null;
|
|
||||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||||
options.mask = mask;
|
|
||||||
params.remove("path");
|
params.remove("path");
|
||||||
if (mask != null) {
|
|
||||||
JsonArray maskArray = new JsonArray();
|
|
||||||
for (Locator locator: mask) {
|
|
||||||
maskArray.add(((LocatorImpl) locator).toProtocol());
|
|
||||||
}
|
|
||||||
params.add("mask", maskArray);
|
|
||||||
}
|
|
||||||
JsonObject json = sendMessage("screenshot", params, timeoutSettings.timeout(options.timeout)).getAsJsonObject();
|
JsonObject json = sendMessage("screenshot", params, timeoutSettings.timeout(options.timeout)).getAsJsonObject();
|
||||||
|
|
||||||
byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString());
|
byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString());
|
||||||
@ -1367,10 +1358,13 @@ public class PageImpl extends ChannelOwner implements Page {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
|
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
|
||||||
|
final LoadState loadState = state == null ? LoadState.LOAD : state;
|
||||||
|
withTitle("Wait for load state \"" + loadState.toString().toLowerCase() + "\"", () -> {
|
||||||
withWaitLogging("Page.waitForLoadState", logger -> {
|
withWaitLogging("Page.waitForLoadState", logger -> {
|
||||||
mainFrame.waitForLoadStateImpl(state, convertType(options, Frame.WaitForLoadStateOptions.class), logger);
|
mainFrame.waitForLoadStateImpl(loadState, convertType(options, Frame.WaitForLoadStateOptions.class), logger);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -66,6 +66,7 @@ class Serialization {
|
|||||||
.registerTypeHierarchyAdapter(JSHandleImpl.class, new HandleSerializer())
|
.registerTypeHierarchyAdapter(JSHandleImpl.class, new HandleSerializer())
|
||||||
.registerTypeAdapter((new TypeToken<Map<String, String>>(){}).getType(), new StringMapSerializer())
|
.registerTypeAdapter((new TypeToken<Map<String, String>>(){}).getType(), new StringMapSerializer())
|
||||||
.registerTypeAdapter((new TypeToken<Map<String, Object>>(){}).getType(), new FirefoxUserPrefsSerializer())
|
.registerTypeAdapter((new TypeToken<Map<String, Object>>(){}).getType(), new FirefoxUserPrefsSerializer())
|
||||||
|
.registerTypeAdapter(LocatorImpl.class, new LocatorImplSerializer())
|
||||||
.registerTypeHierarchyAdapter(Path.class, new PathSerializer()).create();
|
.registerTypeHierarchyAdapter(Path.class, new PathSerializer()).create();
|
||||||
|
|
||||||
static Gson gson() {
|
static Gson gson() {
|
||||||
@ -490,6 +491,13 @@ class Serialization {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class LocatorImplSerializer implements JsonSerializer<LocatorImpl> {
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(LocatorImpl src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
return src.toProtocol();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class SameSiteAdapter extends TypeAdapter<SameSiteAttribute> {
|
private static class SameSiteAdapter extends TypeAdapter<SameSiteAttribute> {
|
||||||
@Override
|
@Override
|
||||||
public void write(JsonWriter out, SameSiteAttribute value) throws IOException {
|
public void write(JsonWriter out, SameSiteAttribute value) throws IOException {
|
||||||
|
@ -23,6 +23,7 @@ import java.net.InetSocketAddress;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
import static com.microsoft.playwright.Utils.copy;
|
import static com.microsoft.playwright.Utils.copy;
|
||||||
@ -40,6 +41,7 @@ public class Server implements HttpHandler {
|
|||||||
private final Map<String, String> csp = Collections.synchronizedMap(new HashMap<>());
|
private final Map<String, String> csp = Collections.synchronizedMap(new HashMap<>());
|
||||||
private final Map<String, HttpHandler> routes = Collections.synchronizedMap(new HashMap<>());
|
private final Map<String, HttpHandler> routes = Collections.synchronizedMap(new HashMap<>());
|
||||||
private final Set<String> gzipRoutes = Collections.synchronizedSet(new HashSet<>());
|
private final Set<String> gzipRoutes = Collections.synchronizedSet(new HashSet<>());
|
||||||
|
private Function<String, InputStream> resourceProvider;
|
||||||
|
|
||||||
private static class Auth {
|
private static class Auth {
|
||||||
public final String user;
|
public final String user;
|
||||||
@ -75,6 +77,8 @@ public class Server implements HttpHandler {
|
|||||||
server.createContext("/", this);
|
server.createContext("/", this);
|
||||||
server.setExecutor(null); // creates a default executor
|
server.setExecutor(null); // creates a default executor
|
||||||
server.start();
|
server.start();
|
||||||
|
// Resources from "src/test/resources/" are copied to "resources/" directory in the jar.
|
||||||
|
resourceProvider = path -> Server.class.getClassLoader().getResourceAsStream("resources" + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
@ -93,6 +97,10 @@ public class Server implements HttpHandler {
|
|||||||
gzipRoutes.add(path);
|
gzipRoutes.add(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setResourceProvider(Function<String, InputStream> resourceProvider) {
|
||||||
|
this.resourceProvider = resourceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
static class Request {
|
static class Request {
|
||||||
public final String url;
|
public final String url;
|
||||||
public final String method;
|
public final String method;
|
||||||
@ -187,18 +195,16 @@ public class Server implements HttpHandler {
|
|||||||
path = "/index.html";
|
path = "/index.html";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resources from "src/test/resources/" are copied to "resources/" directory in the jar.
|
InputStream resource = resourceProvider.apply(path);
|
||||||
String resourcePath = "resources" + path;
|
|
||||||
InputStream resource = getClass().getClassLoader().getResourceAsStream(resourcePath);
|
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
exchange.getResponseHeaders().add("Content-Type", "text/plain");
|
exchange.getResponseHeaders().add("Content-Type", "text/plain");
|
||||||
exchange.sendResponseHeaders(404, 0);
|
exchange.sendResponseHeaders(404, 0);
|
||||||
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
||||||
writer.write("File not found: " + resourcePath);
|
writer.write("File not found: " + path);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
exchange.getResponseHeaders().add("Content-Type", mimeType(new File(resourcePath)));
|
exchange.getResponseHeaders().add("Content-Type", mimeType(new File(path)));
|
||||||
ByteArrayOutputStream body = new ByteArrayOutputStream();
|
ByteArrayOutputStream body = new ByteArrayOutputStream();
|
||||||
OutputStream output = body;
|
OutputStream output = body;
|
||||||
if (gzipRoutes.contains(path)) {
|
if (gzipRoutes.contains(path)) {
|
||||||
|
@ -45,12 +45,12 @@ public class TestBase {
|
|||||||
static final boolean isMac = Utils.getOS() == Utils.OS.MAC;
|
static final boolean isMac = Utils.getOS() == Utils.OS.MAC;
|
||||||
static final boolean isLinux = Utils.getOS() == Utils.OS.LINUX;
|
static final boolean isLinux = Utils.getOS() == Utils.OS.LINUX;
|
||||||
static final boolean isWindows = Utils.getOS() == Utils.OS.WINDOWS;
|
static final boolean isWindows = Utils.getOS() == Utils.OS.WINDOWS;
|
||||||
static final boolean headful;
|
static final boolean headed;
|
||||||
static final SameSiteAttribute defaultSameSiteCookieValue;
|
static final SameSiteAttribute defaultSameSiteCookieValue;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
String headfulEnv = System.getenv("HEADFUL");
|
String headedEnv = System.getenv("HEADED");
|
||||||
headful = headfulEnv != null && !"0".equals(headfulEnv) && !"false".equals(headfulEnv);
|
headed = headedEnv != null && !"0".equals(headedEnv) && !"false".equals(headedEnv);
|
||||||
defaultSameSiteCookieValue = initSameSiteAttribute();
|
defaultSameSiteCookieValue = initSameSiteAttribute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,8 +58,8 @@ public class TestBase {
|
|||||||
Page page;
|
Page page;
|
||||||
BrowserContext context;
|
BrowserContext context;
|
||||||
|
|
||||||
static boolean isHeadful() {
|
static boolean isHeaded() {
|
||||||
return headful;
|
return headed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isChromium() {
|
static boolean isChromium() {
|
||||||
@ -81,7 +81,7 @@ public class TestBase {
|
|||||||
static BrowserType.LaunchOptions createLaunchOptions() {
|
static BrowserType.LaunchOptions createLaunchOptions() {
|
||||||
BrowserType.LaunchOptions options;
|
BrowserType.LaunchOptions options;
|
||||||
options = new BrowserType.LaunchOptions();
|
options = new BrowserType.LaunchOptions();
|
||||||
options.headless = !headful;
|
options.headless = !headed;
|
||||||
options.channel = getBrowserChannelFromEnv();
|
options.channel = getBrowserChannelFromEnv();
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ public class TestBrowserContextCredentials extends TestBase {
|
|||||||
|
|
||||||
static boolean isChromiumHeadedLike() {
|
static boolean isChromiumHeadedLike() {
|
||||||
// --headless=new, the default in all Chromium channels, is like headless.
|
// --headless=new, the default in all Chromium channels, is like headless.
|
||||||
return isChromium() && (isHeadful() || getBrowserChannelFromEnv() != null);
|
return isChromium() && (isHeaded() || getBrowserChannelFromEnv() != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -129,12 +129,12 @@ public class TestBrowserContextProxy extends TestBase {
|
|||||||
context.close();
|
context.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isChromiumHeadful() {
|
static boolean isChromiumHeaded() {
|
||||||
return isChromium() && isHeadful();
|
return isChromium() && isHeaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisabledIf(value="isChromiumHeadful", disabledReason="fixme")
|
@DisabledIf(value="isChromiumHeaded", disabledReason="fixme")
|
||||||
void shouldExcludePatterns() {
|
void shouldExcludePatterns() {
|
||||||
server.setRoute("/target.html", exchange -> {
|
server.setRoute("/target.html", exchange -> {
|
||||||
exchange.sendResponseHeaders(200, 0);
|
exchange.sendResponseHeaders(200, 0);
|
||||||
|
@ -128,7 +128,7 @@ public class TestDefaultBrowserContext2 extends TestBase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldSupportExtraHTTPHeadersOption() throws ExecutionException, InterruptedException {
|
void shouldSupportExtraHTTPHeadersOption() throws ExecutionException, InterruptedException {
|
||||||
// TODO: test.flaky(browserName === "firefox" && headful && platform === "linux", "Intermittent timeout on bots");
|
// TODO: test.flaky(browserName === "firefox" && headed && platform === "linux", "Intermittent timeout on bots");
|
||||||
Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions().setExtraHTTPHeaders(mapOf("foo", "bar")));
|
Page page = launchPersistent(new BrowserType.LaunchPersistentContextOptions().setExtraHTTPHeaders(mapOf("foo", "bar")));
|
||||||
Future<Server.Request> request = server.futureRequest("/empty.html");
|
Future<Server.Request> request = server.futureRequest("/empty.html");
|
||||||
page.navigate(server.EMPTY_PAGE);
|
page.navigate(server.EMPTY_PAGE);
|
||||||
|
@ -93,18 +93,12 @@ public class TestDownload extends TestBase {
|
|||||||
assertTrue(Files.exists(path));
|
assertTrue(Files.exists(path));
|
||||||
byte[] bytes = readAllBytes(path);
|
byte[] bytes = readAllBytes(path);
|
||||||
assertEquals("Hello world", new String(bytes, UTF_8));
|
assertEquals("Hello world", new String(bytes, UTF_8));
|
||||||
if (isChromium()) {
|
|
||||||
assertNotNull(error[0]);
|
|
||||||
assertTrue(error[0].getMessage().contains("net::ERR_ABORTED"));
|
|
||||||
assertEquals("about:blank", page.url());
|
|
||||||
} else if (isWebKit()) {
|
|
||||||
assertNotNull(error[0]);
|
|
||||||
assertTrue(error[0].getMessage().contains("Download is starting"));
|
|
||||||
assertEquals("about:blank", page.url());
|
|
||||||
} else {
|
|
||||||
assertNotNull(error[0]);
|
assertNotNull(error[0]);
|
||||||
|
if (!chromiumVersionLessThan(browser.version(), "140.0.0.0")) {
|
||||||
assertTrue(error[0].getMessage().contains("Download is starting"));
|
assertTrue(error[0].getMessage().contains("Download is starting"));
|
||||||
}
|
}
|
||||||
|
if (!isFirefox())
|
||||||
|
assertEquals("about:blank", page.url());
|
||||||
page.close();
|
page.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,14 +341,14 @@ public class TestDownload extends TestBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static boolean isChromiumHeadful() {
|
static boolean isChromiumHeaded() {
|
||||||
return isChromium() && isHeadful();
|
return isChromium() && isHeaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisabledIf(value="isChromiumHeadful", disabledReason="fixme")
|
@DisabledIf(value="isChromiumHeaded", disabledReason="fixme")
|
||||||
void shouldReportNewWindowDownloads() throws IOException {
|
void shouldReportNewWindowDownloads() throws IOException {
|
||||||
// TODO: - the test fails in headful Chromium as the popup page gets closed along
|
// TODO: - the test fails in headed Chromium as the popup page gets closed along
|
||||||
// with the session before download completed event arrives.
|
// with the session before download completed event arrives.
|
||||||
// - WebKit doesn't close the popup page
|
// - WebKit doesn't close the popup page
|
||||||
Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true));
|
Page page = browser.newPage(new Browser.NewPageOptions().setAcceptDownloads(true));
|
||||||
|
@ -26,12 +26,12 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
|
|
||||||
public class TestElementHandleBoundingBox extends TestBase {
|
public class TestElementHandleBoundingBox extends TestBase {
|
||||||
|
|
||||||
static boolean isFirefoxHeadful() {
|
static boolean isFirefoxHeaded() {
|
||||||
return isFirefox() && isHeadful();
|
return isFirefox() && isHeaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisabledIf(value="isFirefoxHeadful", disabledReason="fail")
|
@DisabledIf(value="isFirefoxHeaded", disabledReason="fail")
|
||||||
void shouldWork() {
|
void shouldWork() {
|
||||||
page.setViewportSize(500, 500);
|
page.setViewportSize(500, 500);
|
||||||
page.navigate(server.PREFIX + "/grid.html");
|
page.navigate(server.PREFIX + "/grid.html");
|
||||||
|
@ -1,88 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.microsoft.playwright;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.condition.EnabledIf;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static com.microsoft.playwright.Utils.mapOf;
|
|
||||||
import static java.util.Arrays.asList;
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
public class TestLaunch extends TestBase {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@BeforeAll
|
|
||||||
// Hide base class method to not launch browser.
|
|
||||||
void launchBrowser() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void createContextAndPage() {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void passEnvVar() {
|
|
||||||
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions();
|
|
||||||
options.setEnv(mapOf("DEBUG", "pw:protocol"));
|
|
||||||
launchBrowser(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean canRunHeaded() {
|
|
||||||
// On linux headed browser requires xvfb.
|
|
||||||
return isHeadful() || isMac || isWindows;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean canRunExtensionTest() {
|
|
||||||
return canRunHeaded() && isChromium();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@EnabledIf(value="com.microsoft.playwright.TestLaunch#canRunExtensionTest", disabledReason="Only Chromium Headed")
|
|
||||||
void shouldReturnBackgroundPages(@TempDir Path tmpDir) throws IOException {
|
|
||||||
Path profileDir = tmpDir.resolve("profile");
|
|
||||||
Files.createDirectories(profileDir);
|
|
||||||
String extensionPath = Paths.get("src/test/resources/simple-extension").toAbsolutePath().toString();
|
|
||||||
initBrowserType();
|
|
||||||
BrowserContext context = browserType.launchPersistentContext(profileDir, new BrowserType.LaunchPersistentContextOptions()
|
|
||||||
.setHeadless(false)
|
|
||||||
.setArgs(asList(
|
|
||||||
"--disable-extensions-except=" + extensionPath,
|
|
||||||
"--load-extension=" + extensionPath
|
|
||||||
)));
|
|
||||||
List<Page> backgroundPages = context.backgroundPages();
|
|
||||||
context.onBackgroundPage(page1 -> backgroundPages.add(page1));
|
|
||||||
context.waitForCondition(() -> !backgroundPages.isEmpty(),
|
|
||||||
new BrowserContext.WaitForConditionOptions().setTimeout(10_000));
|
|
||||||
Page backgroundPage = backgroundPages.get(0);
|
|
||||||
assertNotNull(backgroundPage);
|
|
||||||
assertTrue(context.backgroundPages().contains(backgroundPage));
|
|
||||||
assertFalse(context.pages().contains(backgroundPage));
|
|
||||||
context.close();
|
|
||||||
assertEquals(0, context.pages().size());
|
|
||||||
assertEquals(0, context.backgroundPages().size());
|
|
||||||
}
|
|
||||||
}
|
|
@ -35,13 +35,13 @@ public class TestOptionsFactories {
|
|||||||
public static BrowserType.LaunchOptions createLaunchOptions() {
|
public static BrowserType.LaunchOptions createLaunchOptions() {
|
||||||
BrowserType.LaunchOptions options;
|
BrowserType.LaunchOptions options;
|
||||||
options = new BrowserType.LaunchOptions();
|
options = new BrowserType.LaunchOptions();
|
||||||
options.headless = !getHeadful();
|
options.headless = !getHeaded();
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean getHeadful() {
|
private static boolean getHeaded() {
|
||||||
String headfulEnv = System.getenv("HEADFUL");
|
String headedEnv = System.getenv("HEADED");
|
||||||
return headfulEnv != null && !"0".equals(headfulEnv) && !"false".equals(headfulEnv);
|
return headedEnv != null && !"0".equals(headedEnv) && !"false".equals(headedEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getBrowserName() {
|
public static String getBrowserName() {
|
||||||
|
@ -356,4 +356,10 @@ public class TestPageBasic extends TestBase {
|
|||||||
|
|
||||||
assertTrue(e.getMessage().contains("Can't add a null listener"));
|
assertTrue(e.getMessage().contains("Can't add a null listener"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void pagePauseShouldNotThrow() {
|
||||||
|
page.pause();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -186,6 +186,13 @@ public class TestPageInterception extends TestBase {
|
|||||||
assertTrue(urlMatches("http://playwright.dev", "http://playwright.dev/?x=y", "?x=y"));
|
assertTrue(urlMatches("http://playwright.dev", "http://playwright.dev/?x=y", "?x=y"));
|
||||||
assertTrue(urlMatches("http://playwright.dev/foo/", "http://playwright.dev/foo/bar?x=y", "./bar?x=y"));
|
assertTrue(urlMatches("http://playwright.dev/foo/", "http://playwright.dev/foo/bar?x=y", "./bar?x=y"));
|
||||||
|
|
||||||
|
// Case insensitive matching
|
||||||
|
assertTrue(urlMatches(null, "https://playwright.dev/fooBAR", "HtTpS://pLaYwRiGhT.dEv/fooBAR"));
|
||||||
|
assertTrue(urlMatches("http://ignored", "https://playwright.dev/fooBAR", "HtTpS://pLaYwRiGhT.dEv/fooBAR"));
|
||||||
|
// Path and search query are case-sensitive
|
||||||
|
assertFalse(urlMatches(null, "https://playwright.dev/foobar", "https://playwright.dev/fooBAR"));
|
||||||
|
assertFalse(urlMatches(null, "https://playwright.dev/foobar?a=b", "https://playwright.dev/foobar?A=B"));
|
||||||
|
|
||||||
// This is not supported, we treat ? as a query separator.
|
// This is not supported, we treat ? as a query separator.
|
||||||
assertFalse(urlMatches(null, "http://localhost:8080/Simple/path.js", "http://localhost:8080/?imple/path.js"));
|
assertFalse(urlMatches(null, "http://localhost:8080/Simple/path.js", "http://localhost:8080/?imple/path.js"));
|
||||||
assertFalse(urlMatches(null, "http://playwright.dev/", "http://playwright.?ev"));
|
assertFalse(urlMatches(null, "http://playwright.dev/", "http://playwright.?ev"));
|
||||||
|
@ -40,7 +40,7 @@ import static java.util.Arrays.asList;
|
|||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
|
||||||
// TODO: suite.skip(browserName === "firefox" && headful");
|
// TODO: suite.skip(browserName === "firefox" && headed");
|
||||||
public class TestPageScreenshot extends TestBase {
|
public class TestPageScreenshot extends TestBase {
|
||||||
@Test
|
@Test
|
||||||
void shouldWork() throws IOException {
|
void shouldWork() throws IOException {
|
||||||
@ -136,7 +136,7 @@ public class TestPageScreenshot extends TestBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void maskShouldWork() {
|
void maskShouldWorkForPage() {
|
||||||
page.setViewportSize(500, 500);
|
page.setViewportSize(500, 500);
|
||||||
page.navigate(server.PREFIX + "/grid.html");
|
page.navigate(server.PREFIX + "/grid.html");
|
||||||
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions()
|
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions()
|
||||||
@ -146,6 +146,17 @@ public class TestPageScreenshot extends TestBase {
|
|||||||
assertThrows(AssertionFailedError.class, () -> assertArrayEquals(screenshot, originalScreenshot));
|
assertThrows(AssertionFailedError.class, () -> assertArrayEquals(screenshot, originalScreenshot));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void maskShouldWorkForLocator() {
|
||||||
|
page.navigate(server.PREFIX + "/grid.html");
|
||||||
|
Locator locatorToScreenshot = page.locator("div").first();
|
||||||
|
byte[] screenshot = locatorToScreenshot.screenshot(new Locator.ScreenshotOptions()
|
||||||
|
.setMask(asList(page.locator("img"))));
|
||||||
|
// TODO: toMatchSnapshot is not present in java, so we only checks that masked screenshot is different.
|
||||||
|
byte[] originalScreenshot = locatorToScreenshot.screenshot();
|
||||||
|
assertThrows(AssertionFailedError.class, () -> assertArrayEquals(screenshot, originalScreenshot));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldWorkWithDeviceScaleFactorAndClip() {
|
void shouldWorkWithDeviceScaleFactorAndClip() {
|
||||||
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||||
|
@ -451,7 +451,7 @@ public class TestPageSetInputFiles extends TestBase {
|
|||||||
List<String> relativePathsSorted = new ArrayList<>(webkitRelativePaths);
|
List<String> relativePathsSorted = new ArrayList<>(webkitRelativePaths);
|
||||||
relativePathsSorted.sort(String::compareTo);
|
relativePathsSorted.sort(String::compareTo);
|
||||||
// https://issues.chromium.org/issues/345393164
|
// https://issues.chromium.org/issues/345393164
|
||||||
if (isChromium() && !isHeadful() && chromiumVersionLessThan(browser.version(), "127.0.6533.0")) {
|
if (isChromium() && !isHeaded() && chromiumVersionLessThan(browser.version(), "127.0.6533.0")) {
|
||||||
assertEquals(asList("file-upload-test/file1.txt", "file-upload-test/file2"), relativePathsSorted);
|
assertEquals(asList("file-upload-test/file1.txt", "file-upload-test/file2"), relativePathsSorted);
|
||||||
} else {
|
} else {
|
||||||
assertEquals(asList("file-upload-test/file1.txt", "file-upload-test/file2", "file-upload-test/sub-dir/really.txt"), relativePathsSorted);
|
assertEquals(asList("file-upload-test/file1.txt", "file-upload-test/file2", "file-upload-test/sub-dir/really.txt"), relativePathsSorted);
|
||||||
|
@ -55,12 +55,12 @@ public class TestRequestFulfill extends TestBase {
|
|||||||
assertEquals("Yo, page!", page.evaluate("document.body.textContent"));
|
assertEquals("Yo, page!", page.evaluate("document.body.textContent"));
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isFirefoxHeadful() {
|
static boolean isFirefoxHeaded() {
|
||||||
return isFirefox() && isHeadful();
|
return isFirefox() && isHeaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisabledIf(value="isFirefoxHeadful", disabledReason="skip")
|
@DisabledIf(value="isFirefoxHeaded", disabledReason="skip")
|
||||||
void shouldAllowMockingBinaryResponses() {
|
void shouldAllowMockingBinaryResponses() {
|
||||||
page.route("**/*", route -> {
|
page.route("**/*", route -> {
|
||||||
byte[] imageBuffer;
|
byte[] imageBuffer;
|
||||||
@ -85,9 +85,9 @@ public class TestRequestFulfill extends TestBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisabledIf(value="isFirefoxHeadful", disabledReason="skip")
|
@DisabledIf(value="isFirefoxHeaded", disabledReason="skip")
|
||||||
void shouldAllowMockingSvgWithCharset() {
|
void shouldAllowMockingSvgWithCharset() {
|
||||||
// Firefox headful produces a different image.
|
// Firefox headed produces a different image.
|
||||||
page.route("**/*", route -> {
|
page.route("**/*", route -> {
|
||||||
route.fulfill(new Route.FulfillOptions()
|
route.fulfill(new Route.FulfillOptions()
|
||||||
.setContentType("image/svg+xml ; charset=utf-8")
|
.setContentType("image/svg+xml ; charset=utf-8")
|
||||||
|
@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package com.microsoft.playwright;
|
package com.microsoft.playwright;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
import com.microsoft.playwright.options.AriaRole;
|
import com.microsoft.playwright.options.AriaRole;
|
||||||
import com.microsoft.playwright.options.Location;
|
import com.microsoft.playwright.options.Location;
|
||||||
import com.microsoft.playwright.options.MouseButton;
|
import com.microsoft.playwright.options.MouseButton;
|
||||||
@ -27,18 +25,12 @@ import org.junit.jupiter.api.BeforeAll;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.util.regex.Pattern;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||||
import static java.util.Arrays.asList;
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
public class TestTracing extends TestBase {
|
public class TestTracing extends TestBase {
|
||||||
@ -57,7 +49,7 @@ public class TestTracing extends TestBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCollectTrace1(@TempDir Path tempDir) {
|
void shouldCollectTrace1(@TempDir Path tempDir) throws Exception {
|
||||||
context.tracing().start(new Tracing.StartOptions().setName("test")
|
context.tracing().start(new Tracing.StartOptions().setName("test")
|
||||||
.setScreenshots(true).setSnapshots(true));
|
.setScreenshots(true).setSnapshots(true));
|
||||||
page.navigate(server.EMPTY_PAGE);
|
page.navigate(server.EMPTY_PAGE);
|
||||||
@ -68,10 +60,18 @@ public class TestTracing extends TestBase {
|
|||||||
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile));
|
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile));
|
||||||
|
|
||||||
assertTrue(Files.exists(traceFile));
|
assertTrue(Files.exists(traceFile));
|
||||||
|
TraceViewerPage.showTraceViewer(this.browserType, traceFile, traceViewer -> {
|
||||||
|
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
|
||||||
|
Pattern.compile("Navigate to \"/empty.html\""),
|
||||||
|
Pattern.compile("Set content"),
|
||||||
|
Pattern.compile("Click"),
|
||||||
|
Pattern.compile("Close")
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCollectTwoTraces(@TempDir Path tempDir) {
|
void shouldCollectTwoTraces(@TempDir Path tempDir) throws Exception {
|
||||||
context.tracing().start(new Tracing.StartOptions().setName("test1")
|
context.tracing().start(new Tracing.StartOptions().setName("test1")
|
||||||
.setScreenshots(true).setSnapshots(true));
|
.setScreenshots(true).setSnapshots(true));
|
||||||
page.navigate(server.EMPTY_PAGE);
|
page.navigate(server.EMPTY_PAGE);
|
||||||
@ -89,10 +89,25 @@ public class TestTracing extends TestBase {
|
|||||||
|
|
||||||
assertTrue(Files.exists(traceFile1));
|
assertTrue(Files.exists(traceFile1));
|
||||||
assertTrue(Files.exists(traceFile2));
|
assertTrue(Files.exists(traceFile2));
|
||||||
|
|
||||||
|
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
|
||||||
|
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
|
||||||
|
Pattern.compile("Navigate to \"/empty.html\""),
|
||||||
|
Pattern.compile("Set content"),
|
||||||
|
Pattern.compile("Click")
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
TraceViewerPage.showTraceViewer(this.browserType, traceFile2, traceViewer -> {
|
||||||
|
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
|
||||||
|
Pattern.compile("Double click"),
|
||||||
|
Pattern.compile("Close")
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldWorkWithMultipleChunks(@TempDir Path tempDir) {
|
void shouldWorkWithMultipleChunks(@TempDir Path tempDir) throws Exception {
|
||||||
context.tracing().start(new Tracing.StartOptions().setScreenshots(true).setSnapshots(true));
|
context.tracing().start(new Tracing.StartOptions().setScreenshots(true).setSnapshots(true));
|
||||||
page.navigate(server.PREFIX + "/frames/frame.html");
|
page.navigate(server.PREFIX + "/frames/frame.html");
|
||||||
|
|
||||||
@ -109,28 +124,60 @@ public class TestTracing extends TestBase {
|
|||||||
|
|
||||||
assertTrue(Files.exists(traceFile1));
|
assertTrue(Files.exists(traceFile1));
|
||||||
assertTrue(Files.exists(traceFile2));
|
assertTrue(Files.exists(traceFile2));
|
||||||
|
|
||||||
|
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
|
||||||
|
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
|
||||||
|
Pattern.compile("Set content"),
|
||||||
|
Pattern.compile("Click")
|
||||||
|
});
|
||||||
|
traceViewer.selectSnapshot("After");
|
||||||
|
FrameLocator frame = traceViewer.snapshotFrame("Set content", 0, false);
|
||||||
|
assertThat(frame.locator("button")).hasText("Click");
|
||||||
|
});
|
||||||
|
|
||||||
|
TraceViewerPage.showTraceViewer(this.browserType, traceFile2, traceViewer -> {
|
||||||
|
assertThat(traceViewer.actionTitles()).containsText(new String[] {"Hover"});
|
||||||
|
FrameLocator frame = traceViewer.snapshotFrame("Hover", 0, false);
|
||||||
|
assertThat(frame.locator("button")).hasText("Click");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCollectSources(@TempDir Path tmpDir) throws IOException {
|
void shouldCollectSources(@TempDir Path tmpDir) throws Exception {
|
||||||
Assumptions.assumeTrue(System.getenv("PLAYWRIGHT_JAVA_SRC") != null, "PLAYWRIGHT_JAVA_SRC must point to the directory containing this test source.");
|
Assumptions.assumeTrue(System.getenv("PLAYWRIGHT_JAVA_SRC") != null, "PLAYWRIGHT_JAVA_SRC must point to the directory containing this test source.");
|
||||||
context.tracing().start(new Tracing.StartOptions().setSources(true));
|
context.tracing().start(new Tracing.StartOptions().setSources(true));
|
||||||
page.navigate(server.EMPTY_PAGE);
|
page.navigate(server.EMPTY_PAGE);
|
||||||
page.setContent("<button>Click</button>");
|
page.setContent("<button>Click</button>");
|
||||||
page.click("'Click'");
|
myMethodOuter();
|
||||||
Path trace = tmpDir.resolve("trace1.zip");
|
Path trace = tmpDir.resolve("trace1.zip");
|
||||||
context.tracing().stop(new Tracing.StopOptions().setPath(trace));
|
context.tracing().stop(new Tracing.StopOptions().setPath(trace));
|
||||||
|
|
||||||
Map<String, byte[]> entries = Utils.parseZip(trace);
|
TraceViewerPage.showTraceViewer(this.browserType, trace, traceViewer -> {
|
||||||
Map<String, byte[]> sources = entries.entrySet().stream().filter(e -> e.getKey().endsWith(".txt")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
|
||||||
assertEquals(1, sources.size());
|
Pattern.compile("Navigate to \"/empty.html\""),
|
||||||
|
Pattern.compile("Set content"),
|
||||||
|
Pattern.compile("Click")
|
||||||
|
});
|
||||||
|
traceViewer.showSourceTab();
|
||||||
|
assertThat(traceViewer.stackFrames()).containsText(new Pattern[] {
|
||||||
|
Pattern.compile("myMethodInner"),
|
||||||
|
Pattern.compile("myMethodOuter"),
|
||||||
|
Pattern.compile("shouldCollectSources")
|
||||||
|
});
|
||||||
|
traceViewer.selectAction("Set content");
|
||||||
|
assertThat(traceViewer.page().locator(".source-tab-file-name"))
|
||||||
|
.hasAttribute("title", Pattern.compile(".*TestTracing\\.java"));
|
||||||
|
assertThat(traceViewer.page().locator(".source-line-running"))
|
||||||
|
.containsText("page.setContent(\"<button>Click</button>\");");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
String path = getClass().getName().replace('.', File.separatorChar);
|
private void myMethodOuter() {
|
||||||
String[] srcRoots = System.getenv("PLAYWRIGHT_JAVA_SRC").split(File.pathSeparator);
|
myMethodInner();
|
||||||
// Resolve in the last specified source dir.
|
}
|
||||||
Path sourceFile = Paths.get(srcRoots[srcRoots.length - 1], path + ".java");
|
|
||||||
byte[] thisFile = Files.readAllBytes(sourceFile);
|
private void myMethodInner() {
|
||||||
assertEquals(new String(thisFile, UTF_8), new String(sources.values().iterator().next(), UTF_8));
|
page.getByText("Click").click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -140,7 +187,7 @@ public class TestTracing extends TestBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldRespectTracesDirAndName(@TempDir Path tempDir) {
|
void shouldRespectTracesDirAndName(@TempDir Path tempDir) throws Exception {
|
||||||
Path tracesDir = tempDir.resolve("trace-dir");
|
Path tracesDir = tempDir.resolve("trace-dir");
|
||||||
BrowserType.LaunchOptions options = createLaunchOptions();
|
BrowserType.LaunchOptions options = createLaunchOptions();
|
||||||
options.setTracesDir(tracesDir);
|
options.setTracesDir(tracesDir);
|
||||||
@ -159,6 +206,24 @@ public class TestTracing extends TestBase {
|
|||||||
context.tracing().stop(new Tracing.StopOptions().setPath(tempDir.resolve("trace2.zip")));
|
context.tracing().stop(new Tracing.StopOptions().setPath(tempDir.resolve("trace2.zip")));
|
||||||
assertTrue(Files.exists(tracesDir.resolve("name2.trace")));
|
assertTrue(Files.exists(tracesDir.resolve("name2.trace")));
|
||||||
assertTrue(Files.exists(tracesDir.resolve("name2.network")));
|
assertTrue(Files.exists(tracesDir.resolve("name2.network")));
|
||||||
|
|
||||||
|
TraceViewerPage.showTraceViewer(this.browserType, tempDir.resolve("trace1.zip"), traceViewer -> {
|
||||||
|
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
|
||||||
|
Pattern.compile("Navigate to \"/one-style.html\"")
|
||||||
|
});
|
||||||
|
FrameLocator frame = traceViewer.snapshotFrame("Navigate", 0, false);
|
||||||
|
assertThat(frame.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
|
||||||
|
assertThat(frame.locator("body")).hasText("hello, world!");
|
||||||
|
});
|
||||||
|
|
||||||
|
TraceViewerPage.showTraceViewer(this.browserType, tempDir.resolve("trace2.zip"), traceViewer -> {
|
||||||
|
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
|
||||||
|
Pattern.compile("Navigate to \"/har.html\"")
|
||||||
|
});
|
||||||
|
FrameLocator frame = traceViewer.snapshotFrame("Navigate", 0, false);
|
||||||
|
assertThat(frame.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
|
||||||
|
assertThat(frame.locator("body")).hasText("hello, world!");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,11 +244,9 @@ public class TestTracing extends TestBase {
|
|||||||
context.tracing().groupEnd();
|
context.tracing().groupEnd();
|
||||||
context.tracing().groupEnd();
|
context.tracing().groupEnd();
|
||||||
|
|
||||||
List<TraceEvent> events = parseTraceEvents(traceFile1);
|
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
|
||||||
List<TraceEvent> groups = events.stream().filter(e -> "tracingGroup".equals(e.method)).collect(Collectors.toList());
|
assertThat(traceViewer.actionTitles()).containsText(new String[] {"actual", "Navigate to \"/empty.html\""});
|
||||||
assertEquals(1, groups.size());
|
});
|
||||||
assertEquals("actual", groups.get(0).title);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -202,9 +265,16 @@ public class TestTracing extends TestBase {
|
|||||||
Path traceFile1 = tempDir.resolve("trace1.zip");
|
Path traceFile1 = tempDir.resolve("trace1.zip");
|
||||||
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
|
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
|
||||||
|
|
||||||
List<TraceEvent> events = parseTraceEvents(traceFile1);
|
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
|
||||||
List<String> calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle()).collect(Collectors.toList());
|
traceViewer.expandAction("inner group 1");
|
||||||
assertEquals(asList("outer group", "Frame.goto", "inner group 1", "Frame.click", "inner group 2", "Frame.isVisible"), calls);
|
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
|
||||||
|
Pattern.compile("outer group"),
|
||||||
|
Pattern.compile("Navigate to \"data:"),
|
||||||
|
Pattern.compile("inner group 1"),
|
||||||
|
Pattern.compile("Click"),
|
||||||
|
Pattern.compile("inner group 2"),
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -240,37 +310,36 @@ public class TestTracing extends TestBase {
|
|||||||
Path traceFile1 = tempDir.resolve("trace1.zip");
|
Path traceFile1 = tempDir.resolve("trace1.zip");
|
||||||
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
|
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
|
||||||
|
|
||||||
List<TraceEvent> events = parseTraceEvents(traceFile1);
|
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
|
||||||
List<String> calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle())
|
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
|
||||||
.collect(Collectors.toList());
|
Pattern.compile("Install clock"),
|
||||||
assertEquals(asList(
|
Pattern.compile("Set content"),
|
||||||
"BrowserContext.clockInstall",
|
Pattern.compile("Click"),
|
||||||
"Frame.setContent",
|
Pattern.compile("Click"),
|
||||||
"Frame.click",
|
Pattern.compile("Type"),
|
||||||
"Frame.click",
|
Pattern.compile("Press"),
|
||||||
"Page.keyboardType",
|
Pattern.compile("Key down"),
|
||||||
"Page.keyboardPress",
|
Pattern.compile("Insert"),
|
||||||
"Page.keyboardDown",
|
Pattern.compile("Key up"),
|
||||||
"Page.keyboardInsertText",
|
Pattern.compile("Mouse move"),
|
||||||
"Page.keyboardUp",
|
Pattern.compile("Mouse down"),
|
||||||
"Page.mouseMove",
|
Pattern.compile("Mouse move"),
|
||||||
"Page.mouseDown",
|
Pattern.compile("Mouse wheel"),
|
||||||
"Page.mouseMove",
|
Pattern.compile("Mouse up"),
|
||||||
"Page.mouseWheel",
|
Pattern.compile("Fast forward clock"),
|
||||||
"Page.mouseUp",
|
Pattern.compile("Fast forward clock"),
|
||||||
"BrowserContext.clockFastForward",
|
Pattern.compile("Pause clock"),
|
||||||
"BrowserContext.clockFastForward",
|
Pattern.compile("Run clock"),
|
||||||
"BrowserContext.clockPauseAt",
|
Pattern.compile("Set fixed time"),
|
||||||
"BrowserContext.clockRunFor",
|
Pattern.compile("Set system time"),
|
||||||
"BrowserContext.clockSetFixedTime",
|
Pattern.compile("Resume clock"),
|
||||||
"BrowserContext.clockSetSystemTime",
|
Pattern.compile("Click")
|
||||||
"BrowserContext.clockResume",
|
});
|
||||||
"Frame.click"),
|
});
|
||||||
calls);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldNotRecordNetworkActions(@TempDir Path tempDir) throws IOException {
|
public void shouldNotRecordNetworkActions(@TempDir Path tempDir) throws Exception {
|
||||||
context.tracing().start(new Tracing.StartOptions());
|
context.tracing().start(new Tracing.StartOptions());
|
||||||
|
|
||||||
page.onRequest(request -> {
|
page.onRequest(request -> {
|
||||||
@ -284,41 +353,30 @@ public class TestTracing extends TestBase {
|
|||||||
Path traceFile1 = tempDir.resolve("trace1.zip");
|
Path traceFile1 = tempDir.resolve("trace1.zip");
|
||||||
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
|
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
|
||||||
|
|
||||||
List<TraceEvent> events = parseTraceEvents(traceFile1);
|
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
|
||||||
List<String> calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle())
|
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
|
||||||
.collect(Collectors.toList());
|
Pattern.compile("Navigate to \"/empty.html\"")
|
||||||
assertEquals(asList("Frame.goto"), calls);
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TraceEvent {
|
@Test
|
||||||
String type;
|
public void shouldShowWaitForLoadState(@TempDir Path tempDir) throws Exception {
|
||||||
String name;
|
// https://github.com/microsoft/playwright/issues/37297
|
||||||
String title;
|
|
||||||
@SerializedName("class")
|
|
||||||
String clazz;
|
|
||||||
String method;
|
|
||||||
Double startTime;
|
|
||||||
Double endTime;
|
|
||||||
String callId;
|
|
||||||
|
|
||||||
String renderedTitle() {
|
context.tracing().start(new Tracing.StartOptions());
|
||||||
if (title != null) {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
if (clazz != null && method != null) {
|
|
||||||
return clazz + "." + method;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<TraceEvent> parseTraceEvents(Path traceFile) throws IOException {
|
page.navigate(server.EMPTY_PAGE);
|
||||||
Map<String, byte[]> files = Utils.parseZip(traceFile);
|
page.waitForLoadState();
|
||||||
Map<String, byte[]> traces = files.entrySet().stream().filter(e -> e.getKey().endsWith(".trace")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
|
||||||
assertNotNull(traces.get("trace.trace"));
|
Path traceFile1 = tempDir.resolve("trace1.zip");
|
||||||
return Arrays.stream(new String(traces.get("trace.trace"), UTF_8)
|
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));
|
||||||
.split("\n"))
|
|
||||||
.map(s -> new Gson().fromJson(s, TraceEvent.class))
|
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
|
||||||
.collect(Collectors.toList());
|
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
|
||||||
|
Pattern.compile("Navigate to \"/empty.html\""),
|
||||||
|
Pattern.compile("Wait for load state \"load\""),
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.microsoft.playwright;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import com.microsoft.playwright.impl.driver.Driver;
|
||||||
|
import com.microsoft.playwright.options.AriaRole;
|
||||||
|
|
||||||
|
class TraceViewerPage {
|
||||||
|
private final Page page;
|
||||||
|
|
||||||
|
TraceViewerPage(Page page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
Page page() {
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
Locator actionsTree() {
|
||||||
|
return page.getByTestId("actions-tree");
|
||||||
|
}
|
||||||
|
|
||||||
|
Locator actionTitles() {
|
||||||
|
return page.locator(".action-title");
|
||||||
|
}
|
||||||
|
|
||||||
|
Locator stackFrames() {
|
||||||
|
return this.page.getByRole(AriaRole.LIST, new Page.GetByRoleOptions().setName("stack trace")).getByRole(AriaRole.LISTITEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectAction(String title, int ordinal) {
|
||||||
|
this.actionsTree().getByTitle(title).nth(ordinal).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectAction(String title) {
|
||||||
|
selectAction(title, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectSnapshot(String name) {
|
||||||
|
this.page.getByRole(AriaRole.TAB, new Page.GetByRoleOptions().setName(name)).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameLocator snapshotFrame(String actionName, int ordinal, boolean hasSubframe) {
|
||||||
|
selectAction(actionName, ordinal);
|
||||||
|
while (page.frames().size() < (hasSubframe ? 4 : 3)) {
|
||||||
|
page.waitForTimeout(200);
|
||||||
|
}
|
||||||
|
return page.frameLocator("iframe.snapshot-visible[name=snapshot]");
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameLocator snapshotFrame(String actionName, int ordinal) {
|
||||||
|
return snapshotFrame(actionName, ordinal, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showSourceTab() {
|
||||||
|
page.getByRole(AriaRole.TAB, new Page.GetByRoleOptions().setName("Source")).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
void expandAction(String title) {
|
||||||
|
this.actionsTree().getByRole(AriaRole.TREEITEM, new Locator.GetByRoleOptions().setName(title)).locator(".codicon-chevron-right").click();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void showTraceViewer(BrowserType browserType, Path tracePath, TraceViewerConsumer callback) throws Exception {
|
||||||
|
Path driverDir = Driver.ensureDriverInstalled(java.util.Collections.emptyMap(), true).driverDir();
|
||||||
|
Path traceViewerPath = driverDir.resolve("package").resolve("lib").resolve("vite").resolve("traceViewer");
|
||||||
|
Server traceServer = Server.createHttp(Utils.nextFreePort());
|
||||||
|
traceServer.setResourceProvider(path -> {
|
||||||
|
Path filePath = traceViewerPath.resolve(path.substring(1));
|
||||||
|
if (Files.exists(filePath) && !Files.isDirectory(filePath)) {
|
||||||
|
try {
|
||||||
|
return Files.newInputStream(filePath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
traceServer.setRoute("/trace.zip", exchange -> {
|
||||||
|
exchange.getResponseHeaders().add("Content-Type", "application/zip");
|
||||||
|
exchange.sendResponseHeaders(200, Files.size(tracePath));
|
||||||
|
Files.copy(tracePath, exchange.getResponseBody());
|
||||||
|
exchange.getResponseBody().close();
|
||||||
|
});
|
||||||
|
|
||||||
|
try (Browser browser = browserType.launch(TestBase.createLaunchOptions());
|
||||||
|
BrowserContext context = browser.newContext()) {
|
||||||
|
Page page = context.newPage();
|
||||||
|
page.navigate(traceServer.PREFIX + "/index.html?trace=" + traceServer.PREFIX + "/trace.zip");
|
||||||
|
|
||||||
|
TraceViewerPage traceViewer = new TraceViewerPage(page);
|
||||||
|
callback.accept(traceViewer);
|
||||||
|
} finally {
|
||||||
|
traceServer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface TraceViewerConsumer {
|
||||||
|
void accept(TraceViewerPage traceViewer) throws Exception;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
package com.microsoft.playwright.impl;
|
|
||||||
|
|
||||||
import com.microsoft.playwright.Browser;
|
|
||||||
|
|
||||||
public class ImplUtils {
|
|
||||||
public static boolean isRemoteBrowser(Browser browser) {
|
|
||||||
return ((BrowserImpl) browser).isConnectedOverWebSocket;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
console.log('hey from the content-script');
|
|
||||||
self.thisIsTheContentScript = true;
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
|||||||
// Mock script for background extension
|
|
||||||
window.MAGIC = 42;
|
|
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Simple extension",
|
|
||||||
"version": "0.1",
|
|
||||||
"background": {
|
|
||||||
"scripts": ["index.js"]
|
|
||||||
},
|
|
||||||
"content_scripts": [{
|
|
||||||
"matches": ["<all_urls>"],
|
|
||||||
"css": [],
|
|
||||||
"js": ["content-script.js"]
|
|
||||||
}],
|
|
||||||
"permissions": ["background", "activeTab"],
|
|
||||||
"manifest_version": 2
|
|
||||||
}
|
|
11
pom.xml
11
pom.xml
@ -44,8 +44,8 @@
|
|||||||
<maven.compiler.source>8</maven.compiler.source>
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
<maven.compiler.target>8</maven.compiler.target>
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
<maven.compiler.parameters>true</maven.compiler.parameters>
|
<maven.compiler.parameters>true</maven.compiler.parameters>
|
||||||
<gson.version>2.12.1</gson.version>
|
<gson.version>2.13.1</gson.version>
|
||||||
<junit.version>5.12.1</junit.version>
|
<junit.version>5.13.4</junit.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<websocket.version>1.6.0</websocket.version>
|
<websocket.version>1.6.0</websocket.version>
|
||||||
<slf4j.version>2.0.17</slf4j.version>
|
<slf4j.version>2.0.17</slf4j.version>
|
||||||
@ -118,7 +118,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-clean-plugin</artifactId>
|
<artifactId>maven-clean-plugin</artifactId>
|
||||||
<version>3.4.1</version>
|
<version>3.5.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
@ -148,7 +148,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
<version>3.11.2</version>
|
<version>3.11.3</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<additionalOptions>--allow-script-in-comments</additionalOptions>
|
<additionalOptions>--allow-script-in-comments</additionalOptions>
|
||||||
<failOnError>false</failOnError>
|
<failOnError>false</failOnError>
|
||||||
@ -170,6 +170,7 @@
|
|||||||
junit.jupiter.execution.parallel.config.dynamic.factor=0.5
|
junit.jupiter.execution.parallel.config.dynamic.factor=0.5
|
||||||
</configurationParameters>
|
</configurationParameters>
|
||||||
</properties>
|
</properties>
|
||||||
|
<failIfNoSpecifiedTests>false</failIfNoSpecifiedTests>
|
||||||
<failIfNoTests>false</failIfNoTests>
|
<failIfNoTests>false</failIfNoTests>
|
||||||
<rerunFailingTestsCount>${env.PW_MAX_RETRIES}</rerunFailingTestsCount>
|
<rerunFailingTestsCount>${env.PW_MAX_RETRIES}</rerunFailingTestsCount>
|
||||||
<!-- Activate the use of TCP to transmit events to the plugin and avoid
|
<!-- Activate the use of TCP to transmit events to the plugin and avoid
|
||||||
@ -180,7 +181,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-gpg-plugin</artifactId>
|
<artifactId>maven-gpg-plugin</artifactId>
|
||||||
<version>3.2.7</version>
|
<version>3.2.8</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
@ -1 +1 @@
|
|||||||
1.54.1
|
1.55.0
|
||||||
|
@ -64,7 +64,15 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>3.2.5</version>
|
<version>3.5.3</version>
|
||||||
|
<configuration>
|
||||||
|
<failIfNoSpecifiedTests>false</failIfNoSpecifiedTests>
|
||||||
|
<failIfNoTests>false</failIfNoTests>
|
||||||
|
<rerunFailingTestsCount>${env.PW_MAX_RETRIES}</rerunFailingTestsCount>
|
||||||
|
<!-- Activate the use of TCP to transmit events to the plugin and avoid
|
||||||
|
[WARNING] Corrupted STDOUT by directly writing to native stream in forked JVM -->
|
||||||
|
<forkNode implementation="org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory"/>
|
||||||
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
8
tools/test-spring-boot-starter/package_and_run_async_test.sh
Executable file
8
tools/test-spring-boot-starter/package_and_run_async_test.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set +x
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
mvn package -D skipTests --no-transfer-progress
|
||||||
|
java -jar target/test-spring-boot-starter*.jar --async
|
@ -5,6 +5,9 @@ import org.springframework.boot.CommandLineRunner;
|
|||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class TestApp implements CommandLineRunner {
|
public class TestApp implements CommandLineRunner {
|
||||||
|
|
||||||
@ -14,6 +17,19 @@ public class TestApp implements CommandLineRunner {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(String... args) {
|
public void run(String... args) {
|
||||||
|
if (Arrays.asList(args).contains("--async")) {
|
||||||
|
runAsync();
|
||||||
|
} else {
|
||||||
|
runSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runAsync() {
|
||||||
|
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(this::runSync);
|
||||||
|
voidCompletableFuture.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runSync() {
|
||||||
try (Playwright playwright = Playwright.create()) {
|
try (Playwright playwright = Playwright.create()) {
|
||||||
BrowserType browserType = getBrowserTypeFromEnv(playwright);
|
BrowserType browserType = getBrowserTypeFromEnv(playwright);
|
||||||
System.out.println("Running test with " + browserType.name());
|
System.out.println("Running test with " + browserType.name());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user