Compare commits
40 Commits
v1.19.0
...
release-1.22
| Author | SHA1 | Date | |
|---|---|---|---|
| 64115bfb60 | |||
| 15eefc54af | |||
| abf245ccc7 | |||
| 10592ce5c7 | |||
| ef7f50c48a | |||
| 473b1ce794 | |||
| 98ecb7e0a0 | |||
| 04eb228813 | |||
| 298e01ee80 | |||
| b6b54af13c | |||
| b8d2ccae08 | |||
| 59e7c0cc94 | |||
| 54d0366b9e | |||
| 9845a05544 | |||
| 41fd9a6f75 | |||
| 483cf0d473 | |||
| 1681c410dd | |||
| 9f6860539a | |||
| 536af6b3d8 | |||
| 8ce193d144 | |||
| 7eddd2d2b2 | |||
| 447578c582 | |||
| 58013adfac | |||
| 43d12a7662 | |||
| 1deccbb55d | |||
| 1b2d33402e | |||
| d315e7b5bf | |||
| 7dc22aa08a | |||
| 5b0ef8b7bf | |||
| 43ba37817b | |||
| 4916ba22af | |||
| 94f72694f1 | |||
| be167a161b | |||
| 187d2ad6c6 | |||
| 68a7dbc1e3 | |||
| 1153d473fb | |||
| 00d53bd1ea | |||
| d423733d9d | |||
| e127abb68e | |||
| 5ee8f23380 |
@@ -17,10 +17,9 @@ jobs:
|
||||
username: playwright
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Docker image
|
||||
run: docker build -t playwright-java:localbuild-focal -f Dockerfile.focal .
|
||||
- name: tag & publish
|
||||
run: |
|
||||
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:next
|
||||
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:next-focal
|
||||
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:sha-${{ github.sha }}
|
||||
- name: Set up Docker QEMU for arm64 docker builds
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: arm64
|
||||
- name: publish docker canary
|
||||
run: ./utils/docker/publish_docker.sh canary
|
||||
|
||||
@@ -3,6 +3,11 @@ on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
is_release:
|
||||
required: true
|
||||
type: boolean
|
||||
description: "Is this a release image?"
|
||||
branches:
|
||||
- release-*
|
||||
jobs:
|
||||
@@ -17,20 +22,12 @@ jobs:
|
||||
login-server: playwright.azurecr.io
|
||||
username: playwright
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Set up Docker QEMU for arm64 docker builds
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: arm64
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Docker image
|
||||
run: docker build -t playwright-java:localbuild-focal -f Dockerfile.focal .
|
||||
- name: tag & publish
|
||||
run: |
|
||||
# GITHUB_REF has a form of `refs/tags/v1.3.0`.
|
||||
# TAG_NAME would be `v1.3.0`
|
||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||
if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]];
|
||||
then
|
||||
echo "Wrong TAG_NAME format: $TAG_NAME"
|
||||
exit 1
|
||||
fi
|
||||
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:latest
|
||||
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:focal
|
||||
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:${TAG_NAME}
|
||||
./scripts/tag_image_and_push.sh playwright-java:localbuild-focal playwright.azurecr.io/public/playwright/java:${TAG_NAME}-focal
|
||||
- run: ./utils/docker/publish_docker.sh stable
|
||||
if: (github.event_name != 'workflow_dispatch' && !github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && github.event.inputs.is_release == 'true')
|
||||
- run: ./utils/docker/publish_docker.sh canary
|
||||
if: (github.event_name != 'workflow_dispatch' && github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && github.event.inputs.is_release != 'true')
|
||||
|
||||
+21
-32
@@ -19,38 +19,35 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1.5.0
|
||||
- uses: microsoft/playwright-github-action@v1
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: 8
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: ${{ runner.os }}-m2
|
||||
- name: Download drivers
|
||||
shell: bash
|
||||
run: scripts/download_driver_for_all_platforms.sh
|
||||
- name: Build with Maven
|
||||
run: mvn -B package -D skipTests --no-transfer-progress
|
||||
- name: Build & Install
|
||||
run: mvn -B install -D skipTests --no-transfer-progress
|
||||
- name: Run tests
|
||||
run: mvn test --no-transfer-progress --fail-at-end
|
||||
run: mvn test -DexcludedGroups=isolated --no-transfer-progress --fail-at-end
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
- name: Run tracing tests w/ sources
|
||||
run: mvn test --no-transfer-progress --fail-at-end -D test=*TestTracing*
|
||||
run: mvn test -DexcludedGroups=isolated --no-transfer-progress --fail-at-end -D test=*TestTracing*
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
PLAYWRIGHT_JAVA_SRC: src/test/java
|
||||
- name: Run driver throw tests
|
||||
run: mvn test -Dgroups=driverThrowTest --no-transfer-progress --fail-at-end
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
- name: Test Spring Boot Starter
|
||||
shell: bash
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
run: |
|
||||
mvn -B install -D skipTests --no-transfer-progress
|
||||
cd tools/test-spring-boot-starter
|
||||
mvn package -D skipTests --no-transfer-progress
|
||||
java -jar target/test-spring-boot*.jar
|
||||
@@ -70,7 +67,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1.5.0
|
||||
- uses: microsoft/playwright-github-action@v1
|
||||
- name: Install Media Pack
|
||||
if: matrix.os == 'windows-latest'
|
||||
shell: powershell
|
||||
@@ -80,19 +77,18 @@ jobs:
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: 8
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: ${{ runner.os }}-m2
|
||||
- name: Download drivers
|
||||
shell: bash
|
||||
run: scripts/download_driver_for_all_platforms.sh
|
||||
- name: Build with Maven
|
||||
run: mvn -B package -D skipTests --no-transfer-progress
|
||||
- name: Build & Install
|
||||
run: mvn -B install -D skipTests --no-transfer-progress
|
||||
- name: Run tests
|
||||
run: mvn test --no-transfer-progress --fail-at-end
|
||||
run: mvn test -DexcludedGroups=isolated --no-transfer-progress --fail-at-end
|
||||
env:
|
||||
BROWSER: chromium
|
||||
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
|
||||
- name: Run driver throw tests
|
||||
run: mvn test -Dgroups=driverThrowTest --no-transfer-progress --fail-at-end
|
||||
env:
|
||||
BROWSER: chromium
|
||||
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
|
||||
@@ -106,23 +102,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1.5.0
|
||||
- uses: microsoft/playwright-github-action@v1
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: adopt
|
||||
java-version: 17
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: m2-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: m2
|
||||
- name: Download drivers
|
||||
shell: bash
|
||||
run: scripts/download_driver_for_all_platforms.sh
|
||||
- name: Build with Maven
|
||||
run: mvn -B package -D skipTests --no-transfer-progress
|
||||
- name: Build & Install
|
||||
run: mvn -B install -D skipTests --no-transfer-progress
|
||||
- name: Run tests
|
||||
run: mvn test --no-transfer-progress --fail-at-end
|
||||
env:
|
||||
@@ -132,7 +122,6 @@ jobs:
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
run: |
|
||||
mvn -B install -D skipTests --no-transfer-progress
|
||||
cd tools/test-spring-boot-starter
|
||||
mvn package -D skipTests --no-transfer-progress
|
||||
java -jar target/test-spring-boot*.jar
|
||||
|
||||
@@ -3,14 +3,14 @@ on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/test_docker.yml'
|
||||
- 'Dockerfile*'
|
||||
- '**/Dockerfile*'
|
||||
branches:
|
||||
- main
|
||||
- release-*
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/test_docker.yml
|
||||
- Dockerfile.*
|
||||
- '**/Dockerfile*'
|
||||
- scripts/CLI_VERSION
|
||||
- '**/pom.xml'
|
||||
branches:
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Docker image
|
||||
run: docker build -t playwright-java:localbuild-focal -f Dockerfile.focal .
|
||||
run: bash utils/docker/build.sh --amd64 focal playwright-java:localbuild-focal
|
||||
- name: Test
|
||||
run: |
|
||||
CONTAINER_ID="$(docker run --rm --ipc=host -v $(pwd):/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-focal /bin/bash)"
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
name: "Internal Tests"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release-*
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
name: "trigger"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- run: |
|
||||
curl -X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: token ${GH_TOKEN}" \
|
||||
--data "{\"event_type\": \"playwright_tests_java\", \"client_payload\": {\"ref\": \"${GITHUB_SHA}\"}}" \
|
||||
https://api.github.com/repos/microsoft/playwright-internal/dispatches
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }}
|
||||
@@ -20,13 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1.5.0
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: ${{ runner.os }}-m2
|
||||
- uses: microsoft/playwright-github-action@v1
|
||||
- name: Download drivers
|
||||
run: scripts/download_driver_for_all_platforms.sh
|
||||
- name: Regenerate APIs
|
||||
|
||||
+14
-1
@@ -8,7 +8,7 @@ Install git, Java JDK (version >= 8), Maven (tested with version 3.6.3), on Ubun
|
||||
just run the following command:
|
||||
|
||||
```sh
|
||||
sudo apt-get install git openjdk-11-jdk maven
|
||||
sudo apt-get install git openjdk-11-jdk maven unzip
|
||||
```
|
||||
|
||||
### Getting the Code
|
||||
@@ -49,6 +49,19 @@ Java interfaces for the current driver run the following commands:
|
||||
./scripts/generate_api.sh
|
||||
```
|
||||
|
||||
#### Updating driver version
|
||||
|
||||
Driver version is read from [scripts/CLI_VERSION](https://github.com/microsoft/playwright-java/blob/main/scripts/CLI_VERSION) and can be found in the upstream [GHA build](https://github.com/microsoft/playwright/actions/workflows/publish_canary.yml) logs. To update the driver to a particular version run the following commands:
|
||||
|
||||
```bash
|
||||
cat > scripts/CLI_VERSION
|
||||
<paste new version>
|
||||
^D
|
||||
./scripts/download_driver_for_all_platforms.sh -f
|
||||
./scripts/generate_api.sh
|
||||
./scripts/update_readme.sh
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
- We try to follow [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html)
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
FROM ubuntu:focal
|
||||
|
||||
# === INSTALL JDK and Maven ===
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
openjdk-11-jdk maven
|
||||
|
||||
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
|
||||
|
||||
# Install utilities required for downloading driver
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl unzip
|
||||
|
||||
# === INSTALL playwright maven modules & browsers ===
|
||||
|
||||
# Browsers will remain downloaded in `/ms-playwright`.
|
||||
# Note: make sure to set 777 to the registry so that any user can access
|
||||
# registry.
|
||||
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
|
||||
|
||||
RUN mkdir /ms-playwright && chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH
|
||||
|
||||
RUN mkdir /tmp/pw-java
|
||||
COPY . /tmp/pw-java
|
||||
RUN cd /tmp/pw-java && \
|
||||
./scripts/download_driver_for_all_platforms.sh && \
|
||||
mvn install -D skipTests --no-transfer-progress && \
|
||||
DEBIAN_FRONTEND=noninteractive mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
|
||||
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
|
||||
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
|
||||
-D exec.args="install" -f playwright/pom.xml --no-transfer-progress && \
|
||||
rm -rf /tmp/pw-java
|
||||
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
|
||||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Chromium <!-- GEN:chromium-version -->100.0.4863.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Chromium <!-- GEN:chromium-version -->102.0.5005.40<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->96.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Firefox <!-- GEN:firefox-version -->99.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.19.0-SNAPSHOT</version>
|
||||
<version>1.22.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver-bundle</artifactId>
|
||||
|
||||
@@ -37,11 +37,13 @@ public class DriverJar extends Driver {
|
||||
? Files.createTempDirectory(prefix)
|
||||
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
|
||||
driverTempDir.toFile().deleteOnExit();
|
||||
logMessage("created DriverJar: " + driverTempDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
|
||||
extractDriverToTempDir();
|
||||
logMessage("extracted driver from jar to " + driverPath());
|
||||
if (installBrowsers)
|
||||
installBrowsers(env);
|
||||
}
|
||||
@@ -137,11 +139,17 @@ public class DriverJar extends Driver {
|
||||
|
||||
private static String platformDir() {
|
||||
String name = System.getProperty("os.name").toLowerCase();
|
||||
String arch = System.getProperty("os.arch").toLowerCase();
|
||||
|
||||
if (name.contains("windows")) {
|
||||
return "win32_x64";
|
||||
}
|
||||
if (name.contains("linux")) {
|
||||
return "linux";
|
||||
if (arch.equals("aarch64")) {
|
||||
return "linux-arm64";
|
||||
} else {
|
||||
return "linux";
|
||||
}
|
||||
}
|
||||
if (name.contains("mac os x")) {
|
||||
return "mac";
|
||||
|
||||
@@ -18,16 +18,19 @@ package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.impl.Driver;
|
||||
import com.microsoft.playwright.impl.DriverJar;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class TestInstall {
|
||||
@@ -36,6 +39,20 @@ public class TestInstall {
|
||||
// Clear system property to ensure that the driver is loaded from jar.
|
||||
System.clearProperty("playwright.cli.dir");
|
||||
System.clearProperty("playwright.driver.tmpdir");
|
||||
// Clear system property to ensure that the default driver is loaded.
|
||||
System.clearProperty("playwright.driver.impl");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tags({@Tag("isolated"), @Tag("driverThrowTest")})
|
||||
void shouldThrowWhenBrowserPathIsInvalid(@TempDir Path tmpDir) {
|
||||
Map<String,String> env = new HashMap<>();
|
||||
env.put("PLAYWRIGHT_DOWNLOAD_HOST", "https://127.0.0.127");
|
||||
// Make sure the browsers are not installed yet by pointing at an empty dir.
|
||||
env.put("PLAYWRIGHT_BROWSERS_PATH", tmpDir.toString());
|
||||
|
||||
assertThrows(RuntimeException.class, () -> Driver.ensureDriverInstalled(env, true));
|
||||
assertThrows(RuntimeException.class, () -> Driver.ensureDriverInstalled(env, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -57,4 +74,19 @@ public class TestInstall {
|
||||
DriverJar driver = new DriverJar();
|
||||
assertTrue(driver.driverPath().startsWith(tmpdir), "Driver path: " + driver.driverPath() + " tmp: " + tmpdir);
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightDriverDefaultImpl() {
|
||||
assertDoesNotThrow(() -> Driver.ensureDriverInstalled(Collections.emptyMap(), false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void playwrightDriverAlternativeImpl() {
|
||||
System.setProperty("playwright.driver.impl", "com.microsoft.playwright.impl.AlternativeDriver");
|
||||
RuntimeException thrown =
|
||||
assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> Driver.ensureDriverInstalled(Collections.emptyMap(), false));
|
||||
assertEquals("Failed to create driver", thrown.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.19.0-SNAPSHOT</version>
|
||||
<version>1.22.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>driver</artifactId>
|
||||
|
||||
@@ -18,8 +18,11 @@ package com.microsoft.playwright.impl;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.DriverLogging.logWithTimestamp;
|
||||
|
||||
/**
|
||||
* This class provides access to playwright-cli. It can be either preinstalled
|
||||
* in the host system and its path is passed as a system property or it can be
|
||||
@@ -31,6 +34,7 @@ public abstract class Driver {
|
||||
private static class PreinstalledDriver extends Driver {
|
||||
private final Path driverDir;
|
||||
PreinstalledDriver(Path driverDir) {
|
||||
logMessage("created PreinstalledDriver: " + driverDir);
|
||||
this.driverDir = driverDir;
|
||||
}
|
||||
|
||||
@@ -49,8 +53,11 @@ public abstract class Driver {
|
||||
if (instance == null) {
|
||||
try {
|
||||
instance = createDriver();
|
||||
logMessage("initializing driver");
|
||||
instance.initialize(env, installBrowsers);
|
||||
logMessage("driver initialized.");
|
||||
} catch (Exception exception) {
|
||||
instance = null;
|
||||
throw new RuntimeException("Failed to create driver", exception);
|
||||
}
|
||||
}
|
||||
@@ -92,9 +99,16 @@ public abstract class Driver {
|
||||
return new PreinstalledDriver(Paths.get(pathFromProperty));
|
||||
}
|
||||
|
||||
Class<?> jarDriver = Class.forName("com.microsoft.playwright.impl.DriverJar");
|
||||
String driverImpl =
|
||||
System.getProperty("playwright.driver.impl", "com.microsoft.playwright.impl.DriverJar");
|
||||
Class<?> jarDriver = Class.forName(driverImpl);
|
||||
return (Driver) jarDriver.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
|
||||
abstract Path driverDir();
|
||||
|
||||
protected static void logMessage(String message) {
|
||||
// This matches log format produced by the server.
|
||||
logWithTimestamp("pw:install " + message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.impl;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
class DriverLogging {
|
||||
private static final boolean isEnabled;
|
||||
static {
|
||||
String debug = System.getenv("DEBUG");
|
||||
isEnabled = (debug != null) && debug.contains("pw:install");
|
||||
}
|
||||
|
||||
private static final DateTimeFormatter timestampFormat = DateTimeFormatter.ofPattern(
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneId.of("UTC"));
|
||||
|
||||
static void logWithTimestamp(String message) {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
}
|
||||
// This matches log format produced by the server.
|
||||
String timestamp = ZonedDateTime.now().format(timestampFormat);
|
||||
System.err.println(timestamp + " " + message);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>examples</artifactId>
|
||||
<version>1.19.0-SNAPSHOT</version>
|
||||
<version>1.22.0</version>
|
||||
<name>Playwright Client Examples</name>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 org.example;
|
||||
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
public class SelectorsAndKeyboardManipulation {
|
||||
public static void main(String[] args) {
|
||||
try(Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.firefox().launch();
|
||||
BrowserContext context = browser.newContext();
|
||||
Page page = context.newPage();
|
||||
page.navigate("https://playwright.dev/java/");
|
||||
page.locator("text=SearchK").click();
|
||||
page.locator("[placeholder=\"Search docs\"]").fill("getting started");
|
||||
page.locator("div[role=\"button\"]:has-text(\"CancelIntroductionGetting startedInstallationGetting startedUsageGetting start\")").click();
|
||||
page.waitForSelector("h1:has-text(\"Getting started\")"); // Waits for the new page to load before screenshotting.
|
||||
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("Screenshot.png")));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.19.0-SNAPSHOT</version>
|
||||
<version>1.22.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>playwright</artifactId>
|
||||
|
||||
@@ -21,9 +21,9 @@ import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Exposes API that can be used for the Web API testing. Each Playwright browser context has a APIRequestContext instance
|
||||
* attached which shares cookies with the page context. Its also possible to create a new APIRequestContext instance
|
||||
* manually. For more information see <a href="https://playwright.dev/java/docs/class-apirequestcontext">here</a>.
|
||||
* Exposes API that can be used for the Web API testing. This class is used for creating {@code APIRequestContext} instance which
|
||||
* in turn can be used for sending web requests. An instance of this class can be obtained via {@link Playwright#request
|
||||
* Playwright.request()}. For more information see {@code APIRequestContext}.
|
||||
*/
|
||||
public interface APIRequest {
|
||||
class NewContextOptions {
|
||||
|
||||
@@ -21,9 +21,24 @@ import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* This API is used for the Web API testing. You can use it to trigger API endpoints, configure micro-services, prepare
|
||||
* environment or the service to your e2e test. When used on {@code Page} or a {@code BrowserContext}, this API will automatically use
|
||||
* the cookies from the corresponding {@code BrowserContext}. This means that if you log in using this API, your e2e test will be
|
||||
* logged in and vice versa.
|
||||
* environment or the service to your e2e test.
|
||||
*
|
||||
* <p> Each Playwright browser context has associated with it {@code APIRequestContext} instance which shares cookie storage with the
|
||||
* browser context and can be accessed via {@link BrowserContext#request BrowserContext.request()} or {@link Page#request
|
||||
* Page.request()}. It is also possible to create a new APIRequestContext instance manually by calling {@link
|
||||
* APIRequest#newContext APIRequest.newContext()}.
|
||||
*
|
||||
* <p> **Cookie management**
|
||||
*
|
||||
* <p> {@code APIRequestContext} retuned by {@link BrowserContext#request BrowserContext.request()} and {@link Page#request
|
||||
* Page.request()} shares cookie storage with the corresponding {@code BrowserContext}. Each API request will have {@code Cookie}
|
||||
* header populated with the values from the browser context. If the API response contains {@code Set-Cookie} header it will
|
||||
* automatically update {@code BrowserContext} cookies and requests made from the page will pick them up. This means that if you
|
||||
* log in using this API, your e2e test will be logged in and vice versa.
|
||||
*
|
||||
* <p> If you want API requests to not interfere with the browser cookies you shoud create a new {@code APIRequestContext} by calling
|
||||
* {@link APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext} object will have its own isolated cookie
|
||||
* storage.
|
||||
*/
|
||||
public interface APIRequestContext {
|
||||
class StorageStateOptions {
|
||||
|
||||
@@ -1017,8 +1017,9 @@ public interface Browser extends AutoCloseable {
|
||||
Page newPage(NewPageOptions options);
|
||||
/**
|
||||
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
|
||||
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a
|
||||
* href="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
|
||||
* href="https://playwright.dev/java/docs/api/class-tracing">here</a>.
|
||||
*
|
||||
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
|
||||
* create a trace file that can be opened in Chrome DevTools performance panel.
|
||||
@@ -1036,8 +1037,9 @@ public interface Browser extends AutoCloseable {
|
||||
}
|
||||
/**
|
||||
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
|
||||
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a
|
||||
* href="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
|
||||
* href="https://playwright.dev/java/docs/api/class-tracing">here</a>.
|
||||
*
|
||||
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
|
||||
* create a trace file that can be opened in Chrome DevTools performance panel.
|
||||
@@ -1053,8 +1055,9 @@ public interface Browser extends AutoCloseable {
|
||||
}
|
||||
/**
|
||||
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
|
||||
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a
|
||||
* href="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
|
||||
* href="https://playwright.dev/java/docs/api/class-tracing">here</a>.
|
||||
*
|
||||
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
|
||||
* create a trace file that can be opened in Chrome DevTools performance panel.
|
||||
@@ -1070,8 +1073,9 @@ public interface Browser extends AutoCloseable {
|
||||
void startTracing(Page page, StartTracingOptions options);
|
||||
/**
|
||||
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a href="../trace-viewer">Playwright Tracing</a>
|
||||
* could be found <a href="https://playwright.dev/java/docs/class-tracing">here</a>.
|
||||
* which is a low-level chromium-specific debugging tool. API to control <a
|
||||
* href="https://playwright.dev/java/docs/trace-viewer">Playwright Tracing</a> could be found <a
|
||||
* href="https://playwright.dev/java/docs/api/class-tracing">here</a>.
|
||||
*
|
||||
* <p> Returns the buffer with trace data.
|
||||
*/
|
||||
|
||||
@@ -497,7 +497,6 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* <li> {@code "midi"}</li>
|
||||
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
|
||||
* <li> {@code "notifications"}</li>
|
||||
* <li> {@code "push"}</li>
|
||||
* <li> {@code "camera"}</li>
|
||||
* <li> {@code "microphone"}</li>
|
||||
* <li> {@code "background-sync"}</li>
|
||||
@@ -524,7 +523,6 @@ public interface BrowserContext extends AutoCloseable {
|
||||
* <li> {@code "midi"}</li>
|
||||
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
|
||||
* <li> {@code "notifications"}</li>
|
||||
* <li> {@code "push"}</li>
|
||||
* <li> {@code "camera"}</li>
|
||||
* <li> {@code "microphone"}</li>
|
||||
* <li> {@code "background-sync"}</li>
|
||||
|
||||
@@ -52,8 +52,7 @@ public interface BrowserType {
|
||||
*/
|
||||
public Double slowMo;
|
||||
/**
|
||||
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
|
||||
* disable timeout.
|
||||
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 0} (no timeout).
|
||||
*/
|
||||
public Double timeout;
|
||||
|
||||
@@ -73,8 +72,7 @@ public interface BrowserType {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
|
||||
* disable timeout.
|
||||
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 0} (no timeout).
|
||||
*/
|
||||
public ConnectOptions setTimeout(double timeout) {
|
||||
this.timeout = timeout;
|
||||
@@ -130,7 +128,7 @@ public interface BrowserType {
|
||||
/**
|
||||
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
|
||||
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
|
||||
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
*/
|
||||
public Object channel;
|
||||
/**
|
||||
@@ -222,7 +220,7 @@ public interface BrowserType {
|
||||
/**
|
||||
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
|
||||
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
|
||||
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
*/
|
||||
public LaunchOptions setChannel(BrowserChannel channel) {
|
||||
this.channel = channel;
|
||||
@@ -231,7 +229,7 @@ public interface BrowserType {
|
||||
/**
|
||||
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
|
||||
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
|
||||
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
*/
|
||||
public LaunchOptions setChannel(String channel) {
|
||||
this.channel = channel;
|
||||
@@ -399,7 +397,7 @@ public interface BrowserType {
|
||||
/**
|
||||
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
|
||||
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
|
||||
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
*/
|
||||
public Object channel;
|
||||
/**
|
||||
@@ -625,7 +623,7 @@ public interface BrowserType {
|
||||
/**
|
||||
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
|
||||
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
|
||||
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setChannel(BrowserChannel channel) {
|
||||
this.channel = channel;
|
||||
@@ -634,7 +632,7 @@ public interface BrowserType {
|
||||
/**
|
||||
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
|
||||
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
|
||||
* href="https://playwright.dev/java/docs/browsers/#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
|
||||
*/
|
||||
public LaunchPersistentContextOptions setChannel(String channel) {
|
||||
this.channel = channel;
|
||||
@@ -971,7 +969,7 @@ public interface BrowserType {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This methods attaches Playwright to an existing browser instance.
|
||||
* This method attaches Playwright to an existing browser instance.
|
||||
*
|
||||
* @param wsEndpoint A browser websocket endpoint to connect to.
|
||||
*/
|
||||
@@ -979,13 +977,13 @@ public interface BrowserType {
|
||||
return connect(wsEndpoint, null);
|
||||
}
|
||||
/**
|
||||
* This methods attaches Playwright to an existing browser instance.
|
||||
* This method attaches Playwright to an existing browser instance.
|
||||
*
|
||||
* @param wsEndpoint A browser websocket endpoint to connect to.
|
||||
*/
|
||||
Browser connect(String wsEndpoint, ConnectOptions options);
|
||||
/**
|
||||
* This methods attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
|
||||
* This method attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
|
||||
*
|
||||
* <p> The default browser context is accessible via {@link Browser#contexts Browser.contexts()}.
|
||||
*
|
||||
@@ -998,7 +996,7 @@ public interface BrowserType {
|
||||
return connectOverCDP(endpointURL, null);
|
||||
}
|
||||
/**
|
||||
* This methods attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
|
||||
* This method attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
|
||||
*
|
||||
* <p> The default browser context is accessible via {@link Browser#contexts Browser.contexts()}.
|
||||
*
|
||||
|
||||
@@ -19,7 +19,28 @@ package com.microsoft.playwright;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* {@code ConsoleMessage} objects are dispatched by page via the {@link Page#onConsoleMessage Page.onConsoleMessage()} event.
|
||||
* {@code ConsoleMessage} objects are dispatched by page via the {@link Page#onConsoleMessage Page.onConsoleMessage()} event. For
|
||||
* each console messages logged in the page there will be corresponding event in the Playwright context.
|
||||
* <pre>{@code
|
||||
* // Listen for all System.out.printlns
|
||||
* page.onConsoleMessage(msg -> System.out.println(msg.text()));
|
||||
*
|
||||
* // Listen for all console events and handle errors
|
||||
* page.onConsoleMessage(msg -> {
|
||||
* if ("error".equals(msg.type()))
|
||||
* System.out.println("Error text: " + msg.text());
|
||||
* });
|
||||
*
|
||||
* // Get the next System.out.println
|
||||
* ConsoleMessage msg = page.waitForConsoleMessage(() -> {
|
||||
* // Issue console.log inside the page
|
||||
* page.evaluate("console.log('hello', 42, { foo: 'bar' });");
|
||||
* });
|
||||
*
|
||||
* // Deconstruct console.log arguments
|
||||
* msg.args().get(0).jsonValue() // hello
|
||||
* msg.args().get(1).jsonValue() // 42
|
||||
* }</pre>
|
||||
*/
|
||||
public interface ConsoleMessage {
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -74,50 +74,50 @@ public interface FileChooser {
|
||||
Page page();
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(Path files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(Path files, SetFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(Path[] files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(Path[] files, SetFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(FilePayload files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(FilePayload files, SetFilesOptions options);
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
default void setFiles(FilePayload[] files) {
|
||||
setFiles(files, null);
|
||||
}
|
||||
/**
|
||||
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
|
||||
* they are resolved relative to the the current working directory. For empty array, clears the selected files.
|
||||
* they are resolved relative to the current working directory. For empty array, clears the selected files.
|
||||
*/
|
||||
void setFiles(FilePayload[] files, SetFilesOptions options);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -57,8 +57,9 @@ public interface FrameLocator {
|
||||
*/
|
||||
public Locator has;
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
|
||||
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
|
||||
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
|
||||
* {@code <article><div>Playwright</div></article>}.
|
||||
*/
|
||||
public Object hasText;
|
||||
|
||||
@@ -73,16 +74,18 @@ public interface FrameLocator {
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
|
||||
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
|
||||
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
|
||||
* {@code <article><div>Playwright</div></article>}.
|
||||
*/
|
||||
public LocatorOptions setHasText(String hasText) {
|
||||
this.hasText = hasText;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. For example,
|
||||
* {@code "Playwright"} matches {@code <article><div>Playwright</div></article>}.
|
||||
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
|
||||
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
|
||||
* {@code <article><div>Playwright</div></article>}.
|
||||
*/
|
||||
public LocatorOptions setHasText(Pattern hasText) {
|
||||
this.hasText = hasText;
|
||||
@@ -97,7 +100,7 @@ public interface FrameLocator {
|
||||
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
|
||||
* that iframe.
|
||||
*
|
||||
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors/">working with
|
||||
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
|
||||
* selectors</a> for more details.
|
||||
*/
|
||||
FrameLocator frameLocator(String selector);
|
||||
@@ -108,7 +111,7 @@ public interface FrameLocator {
|
||||
/**
|
||||
* The method finds an element matching the specified selector in the FrameLocator's subtree.
|
||||
*
|
||||
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors/">working with
|
||||
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
|
||||
* selectors</a> for more details.
|
||||
*/
|
||||
default Locator locator(String selector) {
|
||||
@@ -117,12 +120,12 @@ public interface FrameLocator {
|
||||
/**
|
||||
* The method finds an element matching the specified selector in the FrameLocator's subtree.
|
||||
*
|
||||
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors/">working with
|
||||
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
|
||||
* selectors</a> for more details.
|
||||
*/
|
||||
Locator locator(String selector, LocatorOptions options);
|
||||
/**
|
||||
* Returns locator to the n-th matching frame.
|
||||
* Returns locator to the n-th matching frame. It's zero based, {@code nth(0)} selects the first frame.
|
||||
*/
|
||||
FrameLocator nth(int index);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -122,12 +122,12 @@ public interface Mouse {
|
||||
}
|
||||
class MoveOptions {
|
||||
/**
|
||||
* defaults to 1. Sends intermediate {@code mousemove} events.
|
||||
* Defaults to 1. Sends intermediate {@code mousemove} events.
|
||||
*/
|
||||
public Integer steps;
|
||||
|
||||
/**
|
||||
* defaults to 1. Sends intermediate {@code mousemove} events.
|
||||
* Defaults to 1. Sends intermediate {@code mousemove} events.
|
||||
*/
|
||||
public MoveOptions setSteps(int steps) {
|
||||
this.steps = steps;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -70,7 +70,7 @@ public interface Playwright extends AutoCloseable {
|
||||
APIRequest request();
|
||||
/**
|
||||
* Selectors can be used to install custom selector engines. See <a
|
||||
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more information.
|
||||
* href="https://playwright.dev/java/docs/selectors">Working with selectors</a> for more information.
|
||||
*/
|
||||
Selectors selectors();
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,8 @@ import java.util.*;
|
||||
/**
|
||||
* Whenever a network route is set up with {@link Page#route Page.route()} or {@link BrowserContext#route
|
||||
* BrowserContext.route()}, the {@code Route} object allows to handle the route.
|
||||
*
|
||||
* <p> Learn more about <a href="https://playwright.dev/java/docs/network">networking</a>.
|
||||
*/
|
||||
public interface Route {
|
||||
class ResumeOptions {
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Selectors can be used to install custom selector engines. See <a
|
||||
* href="https://playwright.dev/java/docs/selectors/">Working with selectors</a> for more information.
|
||||
* href="https://playwright.dev/java/docs/selectors">Working with selectors</a> for more information.
|
||||
*/
|
||||
public interface Selectors {
|
||||
class RegisterOptions {
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* API for collecting and saving Playwright traces. Playwright traces can be opened in <a
|
||||
* href="https://playwright.dev/java/docs/trace-viewer/">Trace Viewer</a> after Playwright script runs.
|
||||
* href="https://playwright.dev/java/docs/trace-viewer">Trace Viewer</a> after Playwright script runs.
|
||||
*
|
||||
* <p> Start recording a trace before performing actions. At the end, stop tracing and save it to a file.
|
||||
* <pre>{@code
|
||||
@@ -56,7 +56,8 @@ public interface Tracing {
|
||||
public Boolean snapshots;
|
||||
/**
|
||||
* Whether to include source files for trace actions. List of the directories with source code for the application must be
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable.
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by ':' on
|
||||
* other platforms).
|
||||
*/
|
||||
public Boolean sources;
|
||||
/**
|
||||
@@ -92,7 +93,8 @@ public interface Tracing {
|
||||
}
|
||||
/**
|
||||
* Whether to include source files for trace actions. List of the directories with source code for the application must be
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable.
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by ':' on
|
||||
* other platforms).
|
||||
*/
|
||||
public StartOptions setSources(boolean sources) {
|
||||
this.sources = sources;
|
||||
|
||||
+16
-8
@@ -328,7 +328,11 @@ public interface LocatorAssertions {
|
||||
*/
|
||||
void isChecked(IsCheckedOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to a disabled element.
|
||||
* Ensures the {@code Locator} points to a disabled element. Element is disabled if it has "disabled" attribute or is disabled
|
||||
* via <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled">'aria-disabled'</a>.
|
||||
* Note that only native control elements such as HTML {@code button}, {@code input}, {@code select}, {@code textarea}, {@code option}, {@code optgroup} can be
|
||||
* disabled by setting "disabled" attribute. "disabled" attribute on other elements is ignored by the browser.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator("button.submit")).isDisabled();
|
||||
* }</pre>
|
||||
@@ -337,7 +341,11 @@ public interface LocatorAssertions {
|
||||
isDisabled(null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to a disabled element.
|
||||
* Ensures the {@code Locator} points to a disabled element. Element is disabled if it has "disabled" attribute or is disabled
|
||||
* via <a
|
||||
* href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled">'aria-disabled'</a>.
|
||||
* Note that only native control elements such as HTML {@code button}, {@code input}, {@code select}, {@code textarea}, {@code option}, {@code optgroup} can be
|
||||
* disabled by setting "disabled" attribute. "disabled" attribute on other elements is ignored by the browser.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator("button.submit")).isDisabled();
|
||||
* }</pre>
|
||||
@@ -409,7 +417,7 @@ public interface LocatorAssertions {
|
||||
void isFocused(IsFocusedOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to a hidden DOM node, which is the opposite of <a
|
||||
* href="https://playwright.dev/java/docs/actionability/#visible">visible</a>.
|
||||
* href="https://playwright.dev/java/docs/api/actionability#visible">visible</a>.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator(".my-element")).isHidden();
|
||||
* }</pre>
|
||||
@@ -419,27 +427,27 @@ public interface LocatorAssertions {
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to a hidden DOM node, which is the opposite of <a
|
||||
* href="https://playwright.dev/java/docs/actionability/#visible">visible</a>.
|
||||
* href="https://playwright.dev/java/docs/api/actionability#visible">visible</a>.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator(".my-element")).isHidden();
|
||||
* }</pre>
|
||||
*/
|
||||
void isHidden(IsHiddenOptions options);
|
||||
/**
|
||||
* Ensures the {@code Locator} points to a <a href="https://playwright.dev/java/docs/actionability/#visible">visible</a> DOM
|
||||
* Ensures the {@code Locator} points to a <a href="https://playwright.dev/java/docs/api/actionability#visible">visible</a> DOM
|
||||
* node.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator(".my-element")).toBeVisible();
|
||||
* assertThat(page.locator(".my-element")).isVisible();
|
||||
* }</pre>
|
||||
*/
|
||||
default void isVisible() {
|
||||
isVisible(null);
|
||||
}
|
||||
/**
|
||||
* Ensures the {@code Locator} points to a <a href="https://playwright.dev/java/docs/actionability/#visible">visible</a> DOM
|
||||
* Ensures the {@code Locator} points to a <a href="https://playwright.dev/java/docs/api/actionability#visible">visible</a> DOM
|
||||
* node.
|
||||
* <pre>{@code
|
||||
* assertThat(page.locator(".my-element")).toBeVisible();
|
||||
* assertThat(page.locator(".my-element")).isVisible();
|
||||
* }</pre>
|
||||
*/
|
||||
void isVisible(IsVisibleOptions options);
|
||||
|
||||
+2
-2
@@ -24,8 +24,8 @@ import com.microsoft.playwright.impl.LocatorAssertionsImpl;
|
||||
import com.microsoft.playwright.impl.PageAssertionsImpl;
|
||||
|
||||
/**
|
||||
* The {@code PlaywrightAssertions} class provides convenience methods for creating assertions that will wait until the expected
|
||||
* condition is met.
|
||||
* Playwright gives you Web-First Assertions with convenience methods for creating assertions that will wait and retry
|
||||
* until the expected condition is met.
|
||||
*
|
||||
* <p> Consider the following example:
|
||||
* <pre>{@code
|
||||
|
||||
@@ -560,4 +560,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
}
|
||||
listeners.notify(EventType.CLOSE, this);
|
||||
}
|
||||
|
||||
WritableStream createTempFile(String name) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("name", name);
|
||||
JsonObject json = sendMessage("createTempFile", params).getAsJsonObject();
|
||||
return connection.getExistingObject(json.getAsJsonObject("writableStream").get("guid").getAsString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,22 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
// We don't use gson() here as the headers map should be serialized to a json object.
|
||||
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
|
||||
params.addProperty("wsEndpoint", wsEndpoint);
|
||||
|
||||
if (!params.has("headers")) {
|
||||
params.add("headers", new JsonObject());
|
||||
}
|
||||
JsonObject headers = params.get("headers").getAsJsonObject();
|
||||
boolean foundBrowserHeader = false;
|
||||
for (String name : headers.keySet()) {
|
||||
if ("x-playwright-browser".equalsIgnoreCase(name)) {
|
||||
foundBrowserHeader = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundBrowserHeader) {
|
||||
headers.addProperty("x-playwright-browser", name());
|
||||
}
|
||||
|
||||
JsonObject json = sendMessage("connect", params).getAsJsonObject();
|
||||
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
|
||||
Connection connection = new Connection(pipe);
|
||||
|
||||
@@ -302,6 +302,9 @@ public class Connection {
|
||||
case "Worker":
|
||||
result = new WorkerImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "WritableStream":
|
||||
result = new WritableStream(parent, type, guid, initializer);
|
||||
break;
|
||||
default:
|
||||
throw new PlaywrightException("Unknown type " + type);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
import static com.microsoft.playwright.impl.Utils.*;
|
||||
import static com.microsoft.playwright.impl.Utils.addLargeFileUploadParams;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.PNG;
|
||||
|
||||
@@ -299,7 +300,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Frame ownerFrame() {
|
||||
public FrameImpl ownerFrame() {
|
||||
return withLogging("ElementHandle.ownerFrame", () -> {
|
||||
JsonObject json = sendMessage("ownerFrame").getAsJsonObject();
|
||||
if (!json.has("frame")) {
|
||||
@@ -455,7 +456,24 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
|
||||
@Override
|
||||
public void setInputFiles(Path[] files, SetInputFilesOptions options) {
|
||||
setInputFiles(Utils.toFilePayloads(files), options);
|
||||
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
|
||||
}
|
||||
|
||||
void setInputFilesImpl(Path[] files, SetInputFilesOptions options) {
|
||||
FrameImpl frame = ownerFrame();
|
||||
if (frame == null) {
|
||||
throw new Error("Cannot set input files to detached element");
|
||||
}
|
||||
if (hasLargeFile(files)) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
addLargeFileUploadParams(files, params, frame.page().context());
|
||||
sendMessage("setInputFilePaths", params);
|
||||
} else {
|
||||
setInputFilesImpl(Utils.toFilePayloads(files), options);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -469,6 +487,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
}
|
||||
|
||||
void setInputFilesImpl(FilePayload[] files, SetInputFilesOptions options) {
|
||||
checkFilePayloadSize(files);
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ class FileChooserImpl implements FileChooser {
|
||||
|
||||
@Override
|
||||
public void setFiles(Path[] files, SetFilesOptions options) {
|
||||
setFiles(Utils.toFilePayloads(files), options);
|
||||
page.withLogging("FileChooser.setInputFiles",
|
||||
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,6 +70,6 @@ class FileChooserImpl implements FileChooser {
|
||||
@Override
|
||||
public void setFiles(FilePayload[] files, SetFilesOptions options) {
|
||||
page.withLogging("FileChooser.setInputFiles",
|
||||
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
|
||||
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.microsoft.playwright.*;
|
||||
import com.microsoft.playwright.options.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -31,7 +32,7 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
import static com.microsoft.playwright.impl.Utils.*;
|
||||
import static com.microsoft.playwright.options.WaitUntilState.*;
|
||||
import static com.microsoft.playwright.impl.Serialization.*;
|
||||
|
||||
@@ -686,21 +687,32 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
|
||||
}
|
||||
|
||||
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
|
||||
if (hasLargeFile(files)) {
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
addLargeFileUploadParams(files, params, page.context());
|
||||
params.addProperty("selector", selector);
|
||||
sendMessage("setInputFilePaths", params);
|
||||
} else {
|
||||
setInputFilesImpl(selector, Utils.toFilePayloads(files), options);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(String selector, FilePayload files, SetInputFilesOptions options) {
|
||||
setInputFiles(selector, new FilePayload[]{files}, options);
|
||||
}
|
||||
|
||||
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
|
||||
setInputFiles(selector, Utils.toFilePayloads(files), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
|
||||
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
|
||||
}
|
||||
|
||||
void setInputFilesImpl(String selector, FilePayload[] files, SetInputFilesOptions options) {
|
||||
checkFilePayloadSize(files);
|
||||
if (options == null) {
|
||||
options = new SetInputFilesOptions();
|
||||
}
|
||||
@@ -1024,6 +1036,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
return result.get("value").getAsInt();
|
||||
}
|
||||
|
||||
void highlightImpl(String selector) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("selector", selector);
|
||||
sendMessage("highlight", params);
|
||||
}
|
||||
|
||||
protected void handleEvent(String event, JsonObject params) {
|
||||
if ("loadstate".equals(event)) {
|
||||
JsonElement add = params.get("add");
|
||||
|
||||
@@ -257,7 +257,8 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
|
||||
|
||||
@Override
|
||||
public void isChecked(IsCheckedOptions options) {
|
||||
expectTrue("to.be.checked", "Locator expected to be checked", convertType(options, FrameExpectOptions.class));
|
||||
String expression = (options != null && options.checked != null && !options.checked) ? "to.be.unchecked" : "to.be.checked";
|
||||
expectTrue(expression, "Locator expected to be checked", convertType(options, FrameExpectOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.*;
|
||||
@@ -9,8 +8,11 @@ import com.microsoft.playwright.options.FilePayload;
|
||||
import com.microsoft.playwright.options.SelectOption;
|
||||
import com.microsoft.playwright.options.WaitForSelectorState;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -22,6 +24,43 @@ class LocatorImpl implements Locator {
|
||||
private final FrameImpl frame;
|
||||
private final String selector;
|
||||
|
||||
private static class Filters {
|
||||
private final Map<Field, String> filterFieldToEngine = new LinkedHashMap<>();
|
||||
private void addFilter(String name, String engine) throws NoSuchFieldException {
|
||||
filterFieldToEngine.put(LocatorOptions.class.getField(name), engine);
|
||||
}
|
||||
{
|
||||
try {
|
||||
addFilter("has", "has");
|
||||
// addFilter("leftOf", "left-of");
|
||||
// addFilter("rightOf", "right-of");
|
||||
// addFilter("above", "above");
|
||||
// addFilter("below", "below");
|
||||
// addFilter("near", "near");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
String addFiltersToSelector(String selector, LocatorOptions options, Frame frame) {
|
||||
try {
|
||||
for (Map.Entry<Field, String> p : filterFieldToEngine.entrySet()) {
|
||||
LocatorImpl filter = (LocatorImpl) p.getKey().get(options);
|
||||
if (filter == null) {
|
||||
continue;
|
||||
}
|
||||
if (filter.frame != frame) {
|
||||
throw new PlaywrightException("Inner '" + p.getKey().getName() + "' locator must belong to the same frame.");
|
||||
}
|
||||
selector += " >> " + p.getValue() + "=" + gson().toJson(filter.selector);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new PlaywrightException("Unexpected options", e);
|
||||
}
|
||||
return selector;
|
||||
}
|
||||
}
|
||||
private static final Filters filters = new Filters();
|
||||
|
||||
public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
|
||||
this.frame = frame;
|
||||
if (options != null) {
|
||||
@@ -34,14 +73,7 @@ class LocatorImpl implements Locator {
|
||||
selector += " >> :scope:has-text(" + escapeWithQuotes(text) + ")";
|
||||
}
|
||||
}
|
||||
if (options.has != null) {
|
||||
LocatorImpl has = (LocatorImpl) options.has;
|
||||
if (has.frame != frame) {
|
||||
throw new PlaywrightException("Inner 'has' locator must belong to the same frame.");
|
||||
}
|
||||
selector += " >> has=" + gson().toJson(has.selector);
|
||||
}
|
||||
|
||||
selector = filters.addFiltersToSelector(selector, options, frame);
|
||||
}
|
||||
this.selector = selector;
|
||||
}
|
||||
@@ -170,6 +202,11 @@ class LocatorImpl implements Locator {
|
||||
frame.fill(selector, value, convertType(options, Frame.FillOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator filter(FilterOptions options) {
|
||||
return new LocatorImpl(frame, selector, convertType(options,LocatorOptions.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locator first() {
|
||||
return new LocatorImpl(frame, selector + " >> nth=0", null);
|
||||
@@ -196,6 +233,11 @@ class LocatorImpl implements Locator {
|
||||
return frame.getAttribute(selector, name, convertType(options, Frame.GetAttributeOptions.class).setStrict(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void highlight() {
|
||||
frame.highlightImpl(selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hover(HoverOptions options) {
|
||||
if (options == null) {
|
||||
@@ -466,6 +508,15 @@ class LocatorImpl implements Locator {
|
||||
return frame.withLogging("Locator.expect", () -> expectImpl(expression, options));
|
||||
}
|
||||
|
||||
JsonObject toProtocol() {
|
||||
JsonObject result = new JsonObject();
|
||||
JsonObject frameJson = new JsonObject();
|
||||
frameJson.addProperty("guid", frame.guid);
|
||||
result.add("frame", frameJson);
|
||||
result.addProperty("selector", selector);
|
||||
return result;
|
||||
}
|
||||
|
||||
private FrameExpectResult expectImpl(String expression, FrameExpectOptions options) {
|
||||
if (options == null) {
|
||||
options = new FrameExpectOptions();
|
||||
|
||||
@@ -28,11 +28,11 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.convertType;
|
||||
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
|
||||
import static com.microsoft.playwright.options.ScreenshotType.PNG;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.nio.file.Files.readAllBytes;
|
||||
import static java.util.Arrays.asList;
|
||||
@@ -1034,8 +1034,18 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
}
|
||||
}
|
||||
}
|
||||
List<Locator> mask = options.mask;
|
||||
options.mask = null;
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
options.mask = mask;
|
||||
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).getAsJsonObject();
|
||||
|
||||
byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString());
|
||||
|
||||
@@ -43,6 +43,8 @@ class SerializedValue{
|
||||
}
|
||||
O[] o;
|
||||
Number h;
|
||||
Integer id;
|
||||
Integer ref;
|
||||
}
|
||||
|
||||
class SerializedArgument{
|
||||
|
||||
@@ -40,7 +40,10 @@ class Serialization {
|
||||
.registerTypeAdapter(Media.class, new ToLowerCaseSerializer<Media>())
|
||||
.registerTypeAdapter(ForcedColors.class, new ToLowerCaseSerializer<ForcedColors>())
|
||||
.registerTypeAdapter(ReducedMotion.class, new ToLowerCaseAndDashSerializer<ReducedMotion>())
|
||||
.registerTypeAdapter(ScreenshotAnimations.class, new ToLowerCaseSerializer<ScreenshotAnimations>())
|
||||
.registerTypeAdapter(ScreenshotType.class, new ToLowerCaseSerializer<ScreenshotType>())
|
||||
.registerTypeAdapter(ScreenshotScale.class, new ToLowerCaseSerializer<ScreenshotScale>())
|
||||
.registerTypeAdapter(ScreenshotCaret.class, new ToLowerCaseSerializer<ScreenshotCaret>())
|
||||
.registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer<MouseButton>())
|
||||
.registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer<LoadState>())
|
||||
.registerTypeAdapter(WaitUntilState.class, new ToLowerCaseSerializer<WaitUntilState>())
|
||||
@@ -68,82 +71,128 @@ class Serialization {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static SerializedValue serializeValue(Object value, List<JSHandleImpl> handles, int depth) {
|
||||
if (depth > 100) {
|
||||
throw new PlaywrightException("Maximum argument depth exceeded");
|
||||
private static class ValueSerializer {
|
||||
// hashCode() of a map containing itself as a key will throw stackoverflow exception,
|
||||
// so we user wrappers.
|
||||
private static class HashableValue {
|
||||
final Object value;
|
||||
HashableValue(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return value == ((HashableValue) o).value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(value);
|
||||
}
|
||||
}
|
||||
SerializedValue result = new SerializedValue();
|
||||
if (value instanceof JSHandleImpl) {
|
||||
result.h = handles.size();
|
||||
handles.add((JSHandleImpl) value);
|
||||
private final Map<HashableValue, Integer> valueToId = new HashMap<>();
|
||||
private int lastId = 0;
|
||||
private final List<JSHandleImpl> handles = new ArrayList<>();
|
||||
private final SerializedValue serializedValue;
|
||||
|
||||
ValueSerializer(Object value) {
|
||||
serializedValue = serializeValue(value);
|
||||
}
|
||||
|
||||
SerializedArgument toSerializedArgument() {
|
||||
SerializedArgument result = new SerializedArgument();
|
||||
result.value = serializedValue;
|
||||
result.handles = new Channel[handles.size()];
|
||||
int i = 0;
|
||||
for (JSHandleImpl handle : handles) {
|
||||
result.handles[i] = new Channel();
|
||||
result.handles[i].guid = handle.guid;
|
||||
++i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (value == null) {
|
||||
result.v = "undefined";
|
||||
} else if (value instanceof Double) {
|
||||
double d = ((Double) value);
|
||||
if (d == Double.POSITIVE_INFINITY) {
|
||||
result.v = "Infinity";
|
||||
} else if (d == Double.NEGATIVE_INFINITY) {
|
||||
result.v = "-Infinity";
|
||||
} else if (d == -0) {
|
||||
result.v = "-0";
|
||||
} else if (Double.isNaN(d)) {
|
||||
result.v = "NaN";
|
||||
|
||||
private SerializedValue serializeValue(Object value) {
|
||||
SerializedValue result = new SerializedValue();
|
||||
if (value instanceof JSHandleImpl) {
|
||||
result.h = handles.size();
|
||||
handles.add((JSHandleImpl) value);
|
||||
return result;
|
||||
}
|
||||
if (value == null) {
|
||||
result.v = "undefined";
|
||||
} else if (value instanceof Double) {
|
||||
double d = ((Double) value);
|
||||
if (d == Double.POSITIVE_INFINITY) {
|
||||
result.v = "Infinity";
|
||||
} else if (d == Double.NEGATIVE_INFINITY) {
|
||||
result.v = "-Infinity";
|
||||
} else if (d == -0) {
|
||||
result.v = "-0";
|
||||
} else if (Double.isNaN(d)) {
|
||||
result.v = "NaN";
|
||||
} else {
|
||||
result.n = d;
|
||||
}
|
||||
} else if (value instanceof Boolean) {
|
||||
result.b = (Boolean) value;
|
||||
} else if (value instanceof Integer) {
|
||||
result.n = (Integer) value;
|
||||
} else if (value instanceof String) {
|
||||
result.s = (String) value;
|
||||
} else {
|
||||
result.n = d;
|
||||
HashableValue mapKey = new HashableValue(value);
|
||||
Integer id = valueToId.get(mapKey);
|
||||
if (id != null) {
|
||||
result.ref = id;
|
||||
} else {
|
||||
result.id = ++lastId;
|
||||
valueToId.put(mapKey, lastId);
|
||||
if (value instanceof List) {
|
||||
List<SerializedValue> list = new ArrayList<>();
|
||||
for (Object o : (List<?>) value) {
|
||||
list.add(serializeValue(o));
|
||||
}
|
||||
result.a = list.toArray(new SerializedValue[0]);
|
||||
} else if (value instanceof Map) {
|
||||
List<SerializedValue.O> list = new ArrayList<>();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ?> map = (Map<String, ?>) value;
|
||||
for (Map.Entry<String, ?> e : map.entrySet()) {
|
||||
SerializedValue.O o = new SerializedValue.O();
|
||||
o.k = e.getKey();
|
||||
o.v = serializeValue(e.getValue());
|
||||
list.add(o);
|
||||
}
|
||||
result.o = list.toArray(new SerializedValue.O[0]);
|
||||
} else if (value instanceof Object[]) {
|
||||
List<SerializedValue> list = new ArrayList<>();
|
||||
for (Object o : (Object[]) value) {
|
||||
list.add(serializeValue(o));
|
||||
}
|
||||
result.a = list.toArray(new SerializedValue[0]);
|
||||
} else {
|
||||
throw new PlaywrightException("Unsupported type of argument: " + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (value instanceof Boolean) {
|
||||
result.b = (Boolean) value;
|
||||
} else if (value instanceof Integer) {
|
||||
result.n = (Integer) value;
|
||||
} else if (value instanceof String) {
|
||||
result.s = (String) value;
|
||||
} else if (value instanceof List) {
|
||||
List<SerializedValue> list = new ArrayList<>();
|
||||
for (Object o : (List<?>) value) {
|
||||
list.add(serializeValue(o, handles, depth + 1));
|
||||
}
|
||||
result.a = list.toArray(new SerializedValue[0]);
|
||||
} else if (value instanceof Map) {
|
||||
List<SerializedValue.O> list = new ArrayList<>();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ?> map = (Map<String, ?>) value;
|
||||
for (Map.Entry<String, ?> e : map.entrySet()) {
|
||||
SerializedValue.O o = new SerializedValue.O();
|
||||
o.k = e.getKey();
|
||||
o.v = serializeValue(e.getValue(), handles, depth + 1);
|
||||
list.add(o);
|
||||
}
|
||||
result.o = list.toArray(new SerializedValue.O[0]);
|
||||
} else if (value instanceof Object[]) {
|
||||
List<SerializedValue> list = new ArrayList<>();
|
||||
for (Object o : (Object[]) value) {
|
||||
list.add(serializeValue(o, handles, depth + 1));
|
||||
}
|
||||
result.a = list.toArray(new SerializedValue[0]);
|
||||
} else {
|
||||
throw new PlaywrightException("Unsupported type of argument: " + value);
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static SerializedArgument serializeArgument(Object arg) {
|
||||
SerializedArgument result = new SerializedArgument();
|
||||
List<JSHandleImpl> handles = new ArrayList<>();
|
||||
result.value = serializeValue(arg, handles, 0);
|
||||
result.handles = new Channel[handles.size()];
|
||||
int i = 0;
|
||||
for (JSHandleImpl handle : handles) {
|
||||
result.handles[i] = new Channel();
|
||||
result.handles[i].guid = handle.guid;
|
||||
++i;
|
||||
}
|
||||
return result;
|
||||
return new ValueSerializer(arg).toSerializedArgument();
|
||||
}
|
||||
|
||||
static <T> T deserialize(SerializedValue value) {
|
||||
return deserialize(value, new HashMap<>());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> T deserialize(SerializedValue value) {
|
||||
private static <T> T deserialize(SerializedValue value, Map<Integer, Object> idToValue) {
|
||||
if (value.ref != null) {
|
||||
return (T) idToValue.get(value.ref);
|
||||
}
|
||||
if (value.n != null) {
|
||||
if (value.n.doubleValue() == (double) value.n.intValue()) {
|
||||
return (T) Integer.valueOf(value.n.intValue());
|
||||
@@ -174,15 +223,17 @@ class Serialization {
|
||||
}
|
||||
if (value.a != null) {
|
||||
List<Object> list = new ArrayList<>();
|
||||
idToValue.put(value.id, list);
|
||||
for (SerializedValue v : value.a) {
|
||||
list.add(deserialize(v));
|
||||
list.add(deserialize(v, idToValue));
|
||||
}
|
||||
return (T) list;
|
||||
}
|
||||
if (value.o != null) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
idToValue.put(value.id, map);
|
||||
for (SerializedValue.O o : value.o) {
|
||||
map.put(o.k, deserialize(o.v));
|
||||
map.put(o.k, deserialize(o.v, idToValue));
|
||||
}
|
||||
return (T) map;
|
||||
}
|
||||
@@ -209,6 +260,14 @@ class Serialization {
|
||||
}
|
||||
}
|
||||
|
||||
static JsonArray toJsonArray(Path[] files) {
|
||||
JsonArray jsonFiles = new JsonArray();
|
||||
for (Path p : files) {
|
||||
jsonFiles.add(p.toAbsolutePath().toString());
|
||||
}
|
||||
return jsonFiles;
|
||||
}
|
||||
|
||||
static JsonArray toJsonArray(FilePayload[] files) {
|
||||
JsonArray jsonFiles = new JsonArray();
|
||||
for (FilePayload p : files) {
|
||||
|
||||
@@ -28,7 +28,6 @@ import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
class TracingImpl extends ChannelOwner implements Tracing {
|
||||
LocalUtils localUtils;
|
||||
boolean isRemote;
|
||||
private boolean includeSources;
|
||||
|
||||
TracingImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
@@ -90,7 +89,7 @@ class TracingImpl extends ChannelOwner implements Tracing {
|
||||
options = new StartOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
includeSources = options.sources != null;
|
||||
boolean includeSources = options.sources != null && options.sources;
|
||||
if (includeSources) {
|
||||
if (!connection.isCollectingStacks()) {
|
||||
throw new PlaywrightException("Source root directory must be specified via PLAYWRIGHT_JAVA_SRC environment variable when source collection is enabled");
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.options.FilePayload;
|
||||
import com.microsoft.playwright.options.HttpHeader;
|
||||
@@ -23,6 +25,7 @@ import com.microsoft.playwright.options.HttpHeader;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Files;
|
||||
@@ -30,6 +33,8 @@ import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.microsoft.playwright.impl.Serialization.toJsonArray;
|
||||
|
||||
class Utils {
|
||||
static <F, T> T convertType(F f, Class<T> t) {
|
||||
if (f == null) {
|
||||
@@ -144,6 +149,49 @@ class Utils {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
static final int maxUplodBufferSize = 50 * 1024 * 1024;
|
||||
|
||||
static boolean hasLargeFile(Path[] files) {
|
||||
for (Path file: files) {
|
||||
try {
|
||||
if (Files.size(file)> maxUplodBufferSize) {
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Cannot get file size.", e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void addLargeFileUploadParams(Path[] files, JsonObject params, BrowserContextImpl context) {
|
||||
if (context.browser().isRemote) {
|
||||
List<WritableStream> streams = new ArrayList<>();
|
||||
JsonArray jsonStreams = new JsonArray();
|
||||
for (Path path : files) {
|
||||
WritableStream temp = context.createTempFile(path.getFileName().toString());
|
||||
streams.add(temp);
|
||||
try (OutputStream out = temp.stream()) {
|
||||
Files.copy(path, out);
|
||||
} catch (IOException e) {
|
||||
throw new PlaywrightException("Failed to copy file to remote server.", e);
|
||||
}
|
||||
jsonStreams.add(temp.toProtocol());
|
||||
}
|
||||
params.add("streams", jsonStreams);
|
||||
} else {
|
||||
params.add("localPaths", toJsonArray(files));
|
||||
}
|
||||
}
|
||||
|
||||
static void checkFilePayloadSize(FilePayload[] files) {
|
||||
for (FilePayload file: files) {
|
||||
if (file.buffer.length > maxUplodBufferSize) {
|
||||
throw new PlaywrightException("Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static FilePayload[] toFilePayloads(Path[] files) {
|
||||
List<FilePayload> payloads = new ArrayList<>();
|
||||
for (Path file : files) {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
class WritableStream extends ChannelOwner {
|
||||
WritableStream(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
|
||||
OutputStream stream() {
|
||||
return new OutputStream() {
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
write(new byte[] { (byte) b });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
JsonObject params = new JsonObject();
|
||||
ByteBuffer buffer = ByteBuffer.wrap(b, off, len);
|
||||
ByteBuffer encoded = Base64.getEncoder().encode(buffer);
|
||||
params.addProperty("binary", new String(encoded.array(), StandardCharsets.UTF_8));
|
||||
sendMessage("write", params);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
JsonObject toProtocol() {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("guid", guid);
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.options;
|
||||
|
||||
public enum ScreenshotAnimations {
|
||||
DISABLED,
|
||||
ALLOW
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.options;
|
||||
|
||||
public enum ScreenshotCaret {
|
||||
HIDE,
|
||||
INITIAL
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.options;
|
||||
|
||||
public enum ScreenshotScale {
|
||||
CSS,
|
||||
DEVICE
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MultipartFormData {
|
||||
static MultipartFormData parseRequest(HttpExchange exchange) throws IOException {
|
||||
ByteArrayOutputStream bodyBytes = new ByteArrayOutputStream();
|
||||
try (OutputStream output = bodyBytes) {
|
||||
Utils.copy(exchange.getRequestBody(), output);
|
||||
}
|
||||
String body = new String(bodyBytes.toByteArray(), StandardCharsets.UTF_8);
|
||||
String contentType = exchange.getRequestHeaders().get("content-type").get(0);
|
||||
Matcher matcher = Pattern.compile("boundary=(.*)$").matcher(contentType);
|
||||
if (!matcher.find()) {
|
||||
throw new RuntimeException("Boundary not found!");
|
||||
}
|
||||
String boundary = matcher.group(1);
|
||||
return new MultipartFormData(body, boundary);
|
||||
}
|
||||
|
||||
static class Field {
|
||||
final String filename;
|
||||
final String content;
|
||||
|
||||
Field(String filename, String content) {
|
||||
this.filename = filename;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
final List<Field> fields = new ArrayList<>();
|
||||
|
||||
MultipartFormData(String body, String boundary) {
|
||||
String[] parts = Pattern.compile("--" + boundary + "(--)?\r\n", Pattern.MULTILINE).split(body);
|
||||
for (String part : parts) {
|
||||
if (part.trim().length() == 0) {
|
||||
continue;
|
||||
}
|
||||
String[] headersAndContent = Pattern.compile("\r\n\r\n", Pattern.MULTILINE).split(part);
|
||||
if (headersAndContent.length != 2) {
|
||||
throw new RuntimeException("Unexpected format: " + part);
|
||||
}
|
||||
String headers = headersAndContent[0];
|
||||
String filename = null;
|
||||
for (String header: Pattern.compile("\r\n", Pattern.MULTILINE).split(headers)) {
|
||||
Matcher matcher = Pattern.compile("content-disposition: .*filename=\"([^\"]+)\"", Pattern.CASE_INSENSITIVE).matcher(header);
|
||||
if (!matcher.find()) {
|
||||
continue;
|
||||
}
|
||||
filename = matcher.group(1);
|
||||
}
|
||||
String content = headersAndContent[1];
|
||||
content = content.substring(0, content.length() - "\r\n".length());
|
||||
fields.add(new Field(filename, content));
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -346,7 +346,21 @@ public class TestBrowserContextAddCookies extends TestBase {
|
||||
"}", server.CROSS_PROCESS_PREFIX + "/grid.html");
|
||||
page.frames().get(1).evaluate("document.cookie = 'username=John Doe'");
|
||||
page.waitForTimeout(2000);
|
||||
boolean allowsThirdParty = isFirefox();
|
||||
List<Cookie> cookies = context.cookies(server.CROSS_PROCESS_PREFIX + "/grid.html");
|
||||
assertEquals(0, cookies.size());
|
||||
if (allowsThirdParty) {
|
||||
assertJsonEquals("[{\n" +
|
||||
" 'domain': '127.0.0.1',\n" +
|
||||
" 'expires': -1,\n" +
|
||||
" 'httpOnly': false,\n" +
|
||||
" 'name': 'username',\n" +
|
||||
" 'path': '/',\n" +
|
||||
" 'sameSite': 'NONE',\n" +
|
||||
" 'secure': false,\n" +
|
||||
" 'value': 'John Doe'\n" +
|
||||
"}]", cookies);
|
||||
} else {
|
||||
assertEquals(0, cookies.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class TestBrowserContextCookies extends TestBase {
|
||||
" expires: -1,\n" +
|
||||
" httpOnly: false,\n" +
|
||||
" secure: false,\n" +
|
||||
" sameSite: '" + (isChromium() || isFirefox() ? "LAX" : "NONE") +"'\n" +
|
||||
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
|
||||
" }]", cookies);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class TestBrowserContextCookies extends TestBase {
|
||||
assertEquals(timestamp, cookie.expires);
|
||||
assertEquals(false, cookie.httpOnly);
|
||||
assertEquals(false, cookie.secure);
|
||||
if (isChromium() || isFirefox()) {
|
||||
if (isChromium()) {
|
||||
assertEquals(SameSiteAttribute.LAX, cookie.sameSite);
|
||||
} else {
|
||||
assertEquals(SameSiteAttribute.NONE, cookie.sameSite);
|
||||
@@ -146,7 +146,7 @@ public class TestBrowserContextCookies extends TestBase {
|
||||
" expires: -1,\n" +
|
||||
" httpOnly: false,\n" +
|
||||
" secure: false,\n" +
|
||||
" sameSite: '" + (isChromium() || isFirefox() ? "LAX" : "NONE") +"'\n" +
|
||||
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" name: 'username',\n" +
|
||||
@@ -156,7 +156,7 @@ public class TestBrowserContextCookies extends TestBase {
|
||||
" expires: -1,\n" +
|
||||
" httpOnly: false,\n" +
|
||||
" secure: false,\n" +
|
||||
" sameSite: '" + (isChromium() || isFirefox() ? "LAX" : "NONE") +"'\n" +
|
||||
" sameSite: '" + (isChromium() ? "LAX" : "NONE") +"'\n" +
|
||||
" }\n" +
|
||||
"]", cookies);
|
||||
}
|
||||
|
||||
+33
-9
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.sun.net.httpserver.Filter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -33,10 +34,19 @@ public class TestBrowserContextNetworkEvents extends TestBase {
|
||||
page.setContent("<a target=_blank rel=noopener href='/one-style.html'>yo</a>");
|
||||
Page page1 = context.waitForPage(() -> page.click("a"));
|
||||
page1.waitForLoadState();
|
||||
assertEquals(asList(
|
||||
server.EMPTY_PAGE,
|
||||
server.PREFIX + "/one-style.html",
|
||||
server.PREFIX + "/one-style.css"), requests);
|
||||
// In firefox one-style.css is requested multiple times.
|
||||
if (isFirefox()) {
|
||||
assertEquals(asList(
|
||||
server.EMPTY_PAGE,
|
||||
server.PREFIX + "/one-style.html",
|
||||
server.PREFIX + "/one-style.css",
|
||||
server.PREFIX + "/one-style.css"), requests);
|
||||
} else {
|
||||
assertEquals(asList(
|
||||
server.EMPTY_PAGE,
|
||||
server.PREFIX + "/one-style.html",
|
||||
server.PREFIX + "/one-style.css"), requests);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -47,10 +57,19 @@ public class TestBrowserContextNetworkEvents extends TestBase {
|
||||
page.setContent("<a target=_blank rel=noopener href='/one-style.html'>yo</a>");
|
||||
Page page1 = context.waitForPage(() -> page.click("a"));
|
||||
page1.waitForLoadState();
|
||||
assertEquals(asList(
|
||||
server.EMPTY_PAGE,
|
||||
server.PREFIX + "/one-style.html",
|
||||
server.PREFIX + "/one-style.css"), responses);
|
||||
// In firefox one-style.css is requested multiple times.
|
||||
if (isFirefox()) {
|
||||
assertEquals(asList(
|
||||
server.EMPTY_PAGE,
|
||||
server.PREFIX + "/one-style.html",
|
||||
server.PREFIX + "/one-style.css",
|
||||
server.PREFIX + "/one-style.css"), responses);
|
||||
} else {
|
||||
assertEquals(asList(
|
||||
server.EMPTY_PAGE,
|
||||
server.PREFIX + "/one-style.html",
|
||||
server.PREFIX + "/one-style.css"), responses);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -59,7 +78,12 @@ public class TestBrowserContextNetworkEvents extends TestBase {
|
||||
List<Request> failedRequests = new ArrayList<>();
|
||||
context.onRequestFailed(request -> failedRequests.add(request));
|
||||
page.navigate(server.PREFIX + "/one-style.html");
|
||||
assertEquals(1, failedRequests.size());
|
||||
// In firefox one-style.css is requested multiple times.
|
||||
if (isFirefox()) {
|
||||
assertTrue(failedRequests.size() > 0);
|
||||
} else {
|
||||
assertEquals(1, failedRequests.size());
|
||||
}
|
||||
assertTrue(failedRequests.get(0).url().contains("one-style.css"));
|
||||
assertNull(failedRequests.get(0).response());
|
||||
assertEquals("stylesheet", failedRequests.get(0).resourceType());
|
||||
|
||||
@@ -140,8 +140,10 @@ public class TestBrowserContextRoute extends TestBase {
|
||||
|
||||
@Test
|
||||
void shouldOverwritePostBodyWithEmptyString() throws ExecutionException, InterruptedException {
|
||||
boolean[] routeHandled = {false};
|
||||
context.route("**/empty.html", route -> {
|
||||
route.resume(new Route.ResumeOptions().setPostData(""));
|
||||
routeHandled[0] = true;
|
||||
});
|
||||
|
||||
Future<Server.Request> req = server.futureRequest("/empty.html");
|
||||
@@ -153,7 +155,9 @@ public class TestBrowserContextRoute extends TestBase {
|
||||
" });\n" +
|
||||
" })()\n" +
|
||||
" </script>");
|
||||
|
||||
while (!routeHandled[0]) {
|
||||
page.waitForTimeout(100);
|
||||
}
|
||||
byte[] body = req.get().postBody;
|
||||
assertEquals(0, body.length);
|
||||
}
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ public class TestBrowserContextStorageState extends TestBase {
|
||||
" 'expires':-1,\n" +
|
||||
" 'httpOnly':false,\n" +
|
||||
" 'secure':false,\n" +
|
||||
" 'sameSite':'" + (isChromium() || isFirefox() ? "Lax" : "None") + "'\n" +
|
||||
" 'sameSite':'" + (isChromium() ? "Lax" : "None") + "'\n" +
|
||||
" }],\n" +
|
||||
" 'origins':[\n" +
|
||||
" {\n" +
|
||||
|
||||
@@ -30,10 +30,11 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.microsoft.playwright.Utils.mapOf;
|
||||
import static com.microsoft.playwright.Utils.parseTrace;
|
||||
import static com.microsoft.playwright.Utils.*;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
@@ -66,7 +67,7 @@ public class TestBrowserTypeConnect extends TestBase {
|
||||
String cliJs = dir.resolve("package/lib/cli/cli.js").toString();
|
||||
// We launch node process directly instead of using playwright.sh script as killing the script
|
||||
// process will leave node process running and killing it would be more hassle.
|
||||
ProcessBuilder pb = new ProcessBuilder(node, cliJs, "launch-server", browserType.name());
|
||||
ProcessBuilder pb = new ProcessBuilder(node, cliJs, "launch-server", "--browser", browserType.name());
|
||||
pb.directory(dir.toFile());
|
||||
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
BrowserServer result = new BrowserServer();
|
||||
@@ -166,6 +167,7 @@ public class TestBrowserTypeConnect extends TestBase {
|
||||
}
|
||||
assertNotNull(webSocketServer.lastClientHandshake);
|
||||
assertEquals("Playwright", webSocketServer.lastClientHandshake.getFieldValue("User-Agent"));
|
||||
assertEquals(browserType.name(), webSocketServer.lastClientHandshake.getFieldValue("x-playwright-browser"));
|
||||
assertEquals("bar", webSocketServer.lastClientHandshake.getFieldValue("foo"));
|
||||
}
|
||||
}
|
||||
@@ -518,4 +520,44 @@ public class TestBrowserTypeConnect extends TestBase {
|
||||
assertEquals(200, response.status());
|
||||
assertEquals("{\"foo\": \"bar\"}\n", response.text());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUploadLargeFile(@TempDir Path tmpDir) throws IOException, ExecutionException, InterruptedException {
|
||||
Assumptions.assumeTrue(3 <= (Runtime.getRuntime().maxMemory() >> 30), "Fails if max heap size is < 3Gb");
|
||||
page.navigate(server.PREFIX + "/input/fileupload.html");
|
||||
Path uploadFile = tmpDir.resolve("200MB.zip");
|
||||
String str = String.join("", Collections.nCopies(4 * 1024, "A"));
|
||||
|
||||
try (Writer stream = new OutputStreamWriter(Files.newOutputStream(uploadFile))) {
|
||||
for (int i = 0; i < 50 * 1024; i++) {
|
||||
stream.write(str);
|
||||
}
|
||||
}
|
||||
Locator input = page.locator("input[type='file']");
|
||||
JSHandle events = input.evaluateHandle("e => {\n" +
|
||||
" const events = [];\n" +
|
||||
" e.addEventListener('input', () => events.push('input'));\n" +
|
||||
" e.addEventListener('change', () => events.push('change'));\n" +
|
||||
" return events;\n" +
|
||||
" }");
|
||||
input.setInputFiles(uploadFile);
|
||||
assertEquals("200MB.zip", input.evaluate("e => e.files[0].name"));
|
||||
assertEquals(asList("input", "change"), events.evaluate("e => e"));
|
||||
CompletableFuture<MultipartFormData> formData = new CompletableFuture<>();
|
||||
server.setRoute("/upload", exchange -> {
|
||||
try {
|
||||
MultipartFormData multipartFormData = MultipartFormData.parseRequest(exchange);
|
||||
formData.complete(multipartFormData);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
formData.completeExceptionally(e);
|
||||
}
|
||||
exchange.sendResponseHeaders(200, -1);
|
||||
});
|
||||
page.click("input[type=submit]", new Page.ClickOptions().setTimeout(90_000));
|
||||
List<MultipartFormData.Field> fields = formData.get().fields;
|
||||
assertEquals(1, fields.size());
|
||||
assertEquals("200MB.zip", fields.get(0).filename);
|
||||
assertEquals(200 * 1024 * 1024, fields.get(0).content.length());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,6 @@ public class TestElementHandlePress extends TestBase {
|
||||
void shouldWorkWithNumberInput() {
|
||||
page.setContent("<input type='number' value=2 />");
|
||||
page.press("input", "1");
|
||||
assertEquals("12", page.evalOnSelector("input", "input => input.value"));
|
||||
assertEquals(isWebKit() ? "1" : "12", page.evalOnSelector("input", "input => input.value"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,6 @@ public class TestElementHandleType extends TestBase {
|
||||
void shouldWorkWithNumberInput() {
|
||||
page.setContent("<input type='number' value=2 />");
|
||||
page.type("input", "13");
|
||||
assertEquals("132", page.evalOnSelector("input", "input => input.value"));
|
||||
assertEquals(isWebKit() ? "13" : "132", page.evalOnSelector("input", "input => input.value"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public class TestGlobalFetch extends TestBase {
|
||||
Optional<HttpHeader> contentType = response.headersArray().stream().filter(h -> "content-type".equals(h.name.toLowerCase())).findFirst();
|
||||
assertTrue(contentType.isPresent());
|
||||
assertEquals("application/json", contentType.get().value);
|
||||
assertEquals("", response.text());
|
||||
assertEquals("{\"foo\": \"bar\"}\n", response.text());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -118,6 +118,13 @@ public class TestLocatorAssertions extends TestBase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWTextInnerTextPass() {
|
||||
page.setContent("<div id=node>Text <span hidden>garbage</span> content</div>");
|
||||
Locator locator = page.locator("#node");
|
||||
assertThat(locator).hasText("Text content", new LocatorAssertions.HasTextOptions().setUseInnerText(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasTextWTextArrayPass() {
|
||||
page.setContent("<div>Text \n1</div><div>Text 2a</div>");
|
||||
@@ -577,6 +584,13 @@ public class TestLocatorAssertions extends TestBase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void isCheckedFalsePass() {
|
||||
page.setContent("<input type=checkbox></input>");
|
||||
Locator locator = page.locator("input");
|
||||
assertThat(locator).isChecked(new LocatorAssertions.IsCheckedOptions().setChecked(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isDisabledPass() {
|
||||
page.setContent("<button disabled>Text</button>");
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.microsoft.playwright.options.BoundingBox;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class TestLocatorHighlight extends TestBase {
|
||||
@Disabled("Requires isUnderTest to be true https://github.com/microsoft/playwright/pull/12420")
|
||||
@Test
|
||||
void shouldHighlightLocator() {
|
||||
page.setContent("<input type='text' />");
|
||||
page.locator("input").highlight();
|
||||
assertThat(page.locator("x-pw-tooltip")).hasText("input");
|
||||
assertThat(page.locator("x-pw-highlight")).isVisible();
|
||||
BoundingBox box1 = page.locator("input").boundingBox();
|
||||
BoundingBox box2 = page.locator("x-pw-highlight").boundingBox();
|
||||
assertEquals(new Gson().toJson(box2), new Gson().toJson(box1));
|
||||
}
|
||||
}
|
||||
@@ -345,31 +345,13 @@ public class TestPageEvaluate extends TestBase {
|
||||
|
||||
@Test
|
||||
void shouldReturnUndefinedForNonSerializableObjects() {
|
||||
assertEquals(null, page.evaluate("() => window"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailForCircularObject() {
|
||||
Object result = page.evaluate("() => {\n" +
|
||||
" const a = {};\n" +
|
||||
" const b = { a };\n" +
|
||||
" a.b = b;\n" +
|
||||
" return a;\n" +
|
||||
"}");
|
||||
assertNull(result);
|
||||
assertEquals(null, page.evaluate("() => () => {}"));
|
||||
assertEquals("ref: <Window>", page.evaluate("() => window"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBeAbleToThrowATrickyError() {
|
||||
JSHandle windowHandle = page.evaluateHandle("() => window");
|
||||
String errorText = null;
|
||||
try {
|
||||
windowHandle.jsonValue();
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
errorText = e.getMessage();
|
||||
}
|
||||
assertNotNull(errorText);
|
||||
String errorText = "My error";
|
||||
try {
|
||||
page.evaluate("errorText => {\n" +
|
||||
" throw new Error(errorText);\n" +
|
||||
@@ -619,4 +601,23 @@ public class TestPageEvaluate extends TestBase {
|
||||
JSHandle resultHandle = page.evaluateHandle("() => ({ toJSON: () => 'string', data: 'data' })");
|
||||
assertEquals(mapOf("data", "data", "toJSON", emptyMap()), resultHandle.jsonValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAliasWindowDocumentAndNode() {
|
||||
Object object = page.evaluate("[window, document, document.body]");
|
||||
assertEquals(asList("ref: <Window>", "ref: <Document>", "ref: <Node>"), object);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkForCircularObject() {
|
||||
Object result = page.evaluate("() => {\n" +
|
||||
" const a = {};\n" +
|
||||
" a.b = a;\n" +
|
||||
" return a;\n" +
|
||||
" }");
|
||||
|
||||
Map<String, Object> map = (Map<String, Object>) result;
|
||||
assertEquals(1, map.size());
|
||||
assertTrue(map == map.get("b"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,12 @@ public class TestPageEventNetwork extends TestBase {
|
||||
List<Request> failedRequests = new ArrayList<>();
|
||||
page.onRequestFailed(request -> failedRequests.add(request));
|
||||
page.navigate(server.PREFIX + "/one-style.html");
|
||||
assertEquals(1, failedRequests.size());
|
||||
// In firefox one-style.css is requested multiple times.
|
||||
if (isFirefox()) {
|
||||
assertTrue(failedRequests.size() > 0);
|
||||
} else {
|
||||
assertEquals(1, failedRequests.size());
|
||||
}
|
||||
assertTrue(failedRequests.get(0).url().contains("one-style.css"));
|
||||
assertNull(failedRequests.get(0).response());
|
||||
assertEquals("stylesheet", failedRequests.get(0).resourceType());
|
||||
|
||||
@@ -227,5 +227,15 @@ public class TestPageExposeFunction extends TestBase {
|
||||
assertTrue(e.getMessage().contains("exposeBindingHandle supports a single argument, 2 received"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSerializeCycles() {
|
||||
Object[] object = { null };
|
||||
page.exposeBinding("log", (source, obj) -> object[0] = obj[0]);
|
||||
page.evaluate("const a = {}; a.b = a; window.log(a)");
|
||||
Map<String, Object> map = (Map<String, Object>) object[0];
|
||||
assertEquals(1, map.size());
|
||||
assertTrue(map == map.get("b"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,4 +134,19 @@ public class TestPageLocatorQuery extends TestBase {
|
||||
assertThat(page.locator("div", new Page.LocatorOptions()
|
||||
.setHas(page.locator("span")).setHasText("wor"))).hasCount(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSupportLocatorFilter() {
|
||||
page.setContent("<section><div><span>hello</span></div><div><span>world</span></div></section>");
|
||||
assertThat(page.locator("div").filter(new Locator.FilterOptions().setHasText("hello"))).hasCount(1);
|
||||
assertThat(page.locator("div", new Page.LocatorOptions().setHasText("hello")).filter(new Locator.FilterOptions().setHasText("hello"))).hasCount(1);
|
||||
assertThat(page.locator("div", new Page.LocatorOptions().setHasText("hello")).filter(new Locator.FilterOptions().setHasText("world"))).hasCount(0);
|
||||
assertThat(page.locator("section", new Page.LocatorOptions().setHasText("hello")).filter(new Locator.FilterOptions().setHasText("world"))).hasCount(1);
|
||||
assertThat(page.locator("div").filter(new Locator.FilterOptions().setHasText("hello")).locator("span")).hasCount(1);
|
||||
assertThat(page.locator("div").filter(new Locator.FilterOptions().setHas(page.locator("span", new Page.LocatorOptions().setHasText("world"))))).hasCount(1);
|
||||
assertThat(page.locator("div").filter(new Locator.FilterOptions().setHas(page.locator("span")))).hasCount(2);
|
||||
assertThat(page.locator("div").filter(new Locator.FilterOptions()
|
||||
.setHas(page.locator("span"))
|
||||
.setHasText("world"))).hasCount(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ public class TestPageRoute extends TestBase {
|
||||
}
|
||||
assertNotNull(failedRequest[0]);
|
||||
if (isWebKit()) {
|
||||
assertEquals("Request intercepted", failedRequest[0].failure());
|
||||
assertEquals("Blocked by Web Inspector", failedRequest[0].failure());
|
||||
} else if (isFirefox()) {
|
||||
assertEquals("NS_ERROR_OFFLINE", failedRequest[0].failure());
|
||||
} else {
|
||||
@@ -281,7 +281,7 @@ public class TestPageRoute extends TestBase {
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
if (isWebKit())
|
||||
assertTrue(e.getMessage().contains("Request intercepted"));
|
||||
assertTrue(e.getMessage().contains("Blocked by Web Inspector"), e.getMessage());
|
||||
else if (isFirefox())
|
||||
assertTrue(e.getMessage().contains("NS_ERROR_FAILURE"));
|
||||
else
|
||||
@@ -347,7 +347,12 @@ public class TestPageRoute extends TestBase {
|
||||
assertEquals(200, response.status());
|
||||
assertTrue(response.url().contains("one-style.html"));
|
||||
|
||||
assertEquals(2, intercepted.size());
|
||||
// In firefox one-style.css is requested multiple times.
|
||||
if (isFirefox()) {
|
||||
assertTrue(intercepted.size() > 1);
|
||||
} else {
|
||||
assertEquals(2, intercepted.size());
|
||||
}
|
||||
assertEquals("document", intercepted.get(0).resourceType());
|
||||
assertTrue(intercepted.get(0).url().contains("one-style.html"));
|
||||
|
||||
@@ -460,7 +465,12 @@ public class TestPageRoute extends TestBase {
|
||||
});
|
||||
Response response = page.navigate("data:text/html,<link rel='stylesheet' href='" + server.PREFIX + "/fonts?helvetica|arial'/>");
|
||||
assertNull(response);
|
||||
assertEquals(1, requests.size());
|
||||
// In firefox linked resource is requested multiple times.
|
||||
if (isFirefox()) {
|
||||
assertTrue(requests.size() > 0);
|
||||
} else {
|
||||
assertEquals(1, requests.size());
|
||||
}
|
||||
assertEquals(400, (requests.get(0).response()).status());
|
||||
}
|
||||
|
||||
@@ -524,9 +534,7 @@ public class TestPageRoute extends TestBase {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.route("**/cars*", route -> {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
if (route.request().url().endsWith("allow")) {
|
||||
headers.put("access-control-allow-origin", "*");
|
||||
}
|
||||
headers.put("access-control-allow-origin", route.request().url().endsWith("allow") ? "*" : "none");
|
||||
route.fulfill(new Route.FulfillOptions()
|
||||
.setStatus(200)
|
||||
.setContentType("application/json")
|
||||
@@ -690,4 +698,29 @@ public class TestPageRoute extends TestBase {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
assertEquals(1, intercepted[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAddAccessControlAllowOriginByDefaultWhenFulfill() {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.route("**/cars", route -> {
|
||||
route.fulfill(new Route.FulfillOptions()
|
||||
.setContentType("application/json")
|
||||
.setStatus(200)
|
||||
.setBody("[\"electric\",\"gas\"]"));
|
||||
});
|
||||
|
||||
Response response = page.waitForResponse("https://example.com/cars", () -> {
|
||||
Object result = page.evaluate("async () => {\n" +
|
||||
" const response = await fetch('https://example.com/cars', {\n" +
|
||||
" method: 'POST',\n" +
|
||||
" headers: { 'Content-Type': 'application/json' },\n" +
|
||||
" mode: 'cors',\n" +
|
||||
" body: JSON.stringify({ 'number': 1 })\n" +
|
||||
" });\n" +
|
||||
" return response.text();\n" +
|
||||
" }");
|
||||
assertEquals("[\"electric\",\"gas\"]", result);
|
||||
});
|
||||
assertEquals(server.PREFIX, response.headerValue("Access-Control-Allow-Origin"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,23 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.Clip;
|
||||
import com.microsoft.playwright.options.ScreenshotAnimations;
|
||||
import com.microsoft.playwright.options.ScreenshotCaret;
|
||||
import com.microsoft.playwright.options.ScreenshotScale;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.opentest4j.AssertionFailedError;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static com.microsoft.playwright.options.ScreenshotAnimations.DISABLED;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
// TODO: suite.skip(browserName === "firefox" && headful");
|
||||
public class TestPageScreenshot extends TestBase {
|
||||
@@ -50,4 +59,191 @@ public class TestPageScreenshot extends TestBase {
|
||||
assertEquals(100, image.getHeight());
|
||||
// expect(screenshot).toMatchSnapshot("screenshot-clip-rect.png");
|
||||
}
|
||||
|
||||
static private void rafraf(Page page) {
|
||||
// Do a double raf since single raf does not
|
||||
// actually guarantee a new animation frame.
|
||||
page.evaluate("() => new Promise(x => {\n" +
|
||||
" requestAnimationFrame(() => requestAnimationFrame(x));\n" +
|
||||
" })");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotCaptureInfiniteCssAnimation() {
|
||||
page.navigate(server.PREFIX + "/rotate-z.html");
|
||||
Locator div = page.locator("div");
|
||||
byte[] screenshot = div.screenshot(new Locator.ScreenshotOptions().setAnimations(DISABLED));
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
rafraf(page);
|
||||
byte[] newScreenshot = div.screenshot(new Locator.ScreenshotOptions().setAnimations(DISABLED));
|
||||
assertArrayEquals(screenshot, newScreenshot);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotCapturePseudoElementCssAnimation() {
|
||||
page.navigate(server.PREFIX + "/rotate-pseudo.html");
|
||||
Locator div = page.locator("div");
|
||||
byte[] screenshot = div.screenshot(new Locator.ScreenshotOptions().setAnimations(DISABLED));
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
rafraf(page);
|
||||
byte[] newScreenshot = div.screenshot(new Locator.ScreenshotOptions().setAnimations(DISABLED));
|
||||
assertArrayEquals(screenshot, newScreenshot);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotCaptureCssAnimationsInShadowDOM() {
|
||||
page.navigate(server.PREFIX + "/rotate-z-shadow-dom.html");
|
||||
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions().setAnimations(DISABLED));
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
rafraf(page);
|
||||
byte[] newScreenshot = page.screenshot(new Page.ScreenshotOptions().setAnimations(DISABLED));
|
||||
assertArrayEquals(screenshot, newScreenshot);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResumeInfiniteAnimations() {
|
||||
page.navigate(server.PREFIX + "/rotate-z.html");
|
||||
page.screenshot(new Page.ScreenshotOptions().setAnimations(DISABLED));
|
||||
byte[] buffer1 = page.screenshot();
|
||||
rafraf(page);
|
||||
byte[] buffer2 = page.screenshot();
|
||||
try {
|
||||
assertArrayEquals(buffer1, buffer2);
|
||||
} catch (AssertionFailedError e) {
|
||||
return;
|
||||
}
|
||||
fail("Screenshots are equal");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotCaptureInfiniteWebAnimations() {
|
||||
page.navigate(server.PREFIX + "/web-animation.html");
|
||||
Locator div = page.locator("div");
|
||||
byte[] screenshot = div.screenshot(new Locator.ScreenshotOptions().setAnimations(DISABLED));
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
rafraf(page);
|
||||
byte[] newScreenshot = div.screenshot(new Locator.ScreenshotOptions().setAnimations(DISABLED));
|
||||
assertArrayEquals(screenshot, newScreenshot);
|
||||
}
|
||||
// Should resume infinite web animation.
|
||||
byte[] buffer1 = page.screenshot();
|
||||
rafraf(page);
|
||||
byte[] buffer2 = page.screenshot();
|
||||
try {
|
||||
assertArrayEquals(buffer1, buffer2);
|
||||
} catch (AssertionFailedError e) {
|
||||
return;
|
||||
}
|
||||
fail("Screenshots are equal");
|
||||
}
|
||||
|
||||
@Test
|
||||
void maskShouldWork() {
|
||||
page.setViewportSize(500, 500);
|
||||
page.navigate(server.PREFIX + "/grid.html");
|
||||
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions()
|
||||
.setMask(asList(page.locator("div").nth(5))));
|
||||
// TODO: toMatchSnapshot is not present in java, so we only checks that masked screenshot is different.
|
||||
byte[] originalScreenshot = page.screenshot();
|
||||
try {
|
||||
assertArrayEquals(screenshot, originalScreenshot);
|
||||
} catch (AssertionFailedError e) {
|
||||
return;
|
||||
}
|
||||
fail("Screenshots are equal");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkWithDeviceScaleFactorAndClip() {
|
||||
try (BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||
.setViewportSize(500, 500).setDeviceScaleFactor(3))) {
|
||||
Page page = context.newPage();
|
||||
page.navigate(server.PREFIX + "/grid.html");
|
||||
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions().setClip(50, 100, 150, 100));
|
||||
assertNotNull(screenshot);
|
||||
// TODO:
|
||||
// expect(screenshot).toMatchSnapshot("screenshot-device-scale-factor-clip.png");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkWithDeviceScaleFactorAndScaleCss() {
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||
.setViewportSize(320, 480).setDeviceScaleFactor(2));
|
||||
Page page = context.newPage();
|
||||
page.navigate(server.PREFIX + "/grid.html");
|
||||
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions().setScale(ScreenshotScale.CSS));
|
||||
assertNotNull(screenshot);
|
||||
// TODO:
|
||||
// expect(screenshot).toMatchSnapshot("screenshot-device-scale-factor-css-size.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkWithDeviceScaleFactorAndScaleDevice() {
|
||||
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||
.setViewportSize(320, 480).setDeviceScaleFactor(2));
|
||||
Page page = context.newPage();
|
||||
page.navigate(server.PREFIX + "/grid.html");
|
||||
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions().setScale(ScreenshotScale.DEVICE));
|
||||
assertNotNull(screenshot);
|
||||
// TODO:
|
||||
// expect(screenshot).toMatchSnapshot("screenshot-device-scale-factor-device-size.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotCaptureBlinkingCaretByDefault() {
|
||||
page.setContent("<!-- Refer to stylesheet from other origin. Accessing this\n" +
|
||||
" stylesheet rules will throw.\n" +
|
||||
" -->\n" +
|
||||
" <link rel=stylesheet href=\"" + server.CROSS_PROCESS_PREFIX + "/injectedstyle.css\">\n" +
|
||||
" <!-- make life harder: define caret color in stylesheet -->\n" +
|
||||
" <style>\n" +
|
||||
" div {\n" +
|
||||
" caret-color: #000 !important;\n" +
|
||||
" }\n" +
|
||||
" </style>\n" +
|
||||
" <div contenteditable=\"true\"></div>\n");
|
||||
Locator div = page.locator("div");
|
||||
div.type("foo bar");
|
||||
byte[] screenshot = div.screenshot();
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
// Caret blinking time is set to 500ms.
|
||||
// Try to capture variety of screenshots to make
|
||||
// sure we don"t capture blinking caret.
|
||||
page.waitForTimeout(150);
|
||||
byte[] newScreenshot = div.screenshot();
|
||||
assertArrayEquals(screenshot, newScreenshot);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCaptureBlinkingCaretIfExplicitlyAskedFor() {
|
||||
page.setContent(" <!-- Refer to stylesheet from other origin. Accessing this\n" +
|
||||
" stylesheet rules will throw.\n" +
|
||||
" -->\n" +
|
||||
" <link rel=stylesheet href=\"" + server.CROSS_PROCESS_PREFIX + "/injectedstyle.css'}\">\n" +
|
||||
" <!-- make life harder: define caret color in stylesheet -->\n" +
|
||||
" <style>\n" +
|
||||
" div {\n" +
|
||||
" caret-color: #000 !important;\n" +
|
||||
" }\n" +
|
||||
" </style>\n" +
|
||||
" <div contenteditable=\"true\"></div>\n");
|
||||
Locator div = page.locator("div");
|
||||
div.type("foo bar");
|
||||
byte[] screenshot = div.screenshot();
|
||||
boolean hasDifferentScreenshots = false;
|
||||
for (int i = 0; !hasDifferentScreenshots && i < 10; ++i) {
|
||||
// Caret blinking time is set to 500ms.
|
||||
// Try to capture variety of screenshots to make
|
||||
// sure we capture blinking caret.
|
||||
page.waitForTimeout(150);
|
||||
byte[] newScreenshot = div.screenshot(new Locator.ScreenshotOptions().setCaret(ScreenshotCaret.INITIAL));
|
||||
hasDifferentScreenshots = !Arrays.equals(newScreenshot, screenshot);
|
||||
}
|
||||
assertTrue(hasDifferentScreenshots);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,22 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import com.microsoft.playwright.options.FilePayload;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
@@ -49,6 +56,46 @@ public class TestPageSetInputFiles extends TestBase {
|
||||
"}", input));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUploadLargeFile(@TempDir Path tmpDir) throws IOException, ExecutionException, InterruptedException {
|
||||
Assumptions.assumeTrue(3 <= (Runtime.getRuntime().maxMemory() >> 30), "Fails if max heap size is < 3Gb");
|
||||
page.navigate(server.PREFIX + "/input/fileupload.html");
|
||||
Path uploadFile = tmpDir.resolve("200MB.zip");
|
||||
String str = String.join("", Collections.nCopies(4 * 1024, "A"));
|
||||
|
||||
try (Writer stream = new OutputStreamWriter(Files.newOutputStream(uploadFile))) {
|
||||
for (int i = 0; i < 50 * 1024; i++) {
|
||||
stream.write(str);
|
||||
}
|
||||
}
|
||||
Locator input = page.locator("input[type='file']");
|
||||
JSHandle events = input.evaluateHandle("e => {\n" +
|
||||
" const events = [];\n" +
|
||||
" e.addEventListener('input', () => events.push('input'));\n" +
|
||||
" e.addEventListener('change', () => events.push('change'));\n" +
|
||||
" return events;\n" +
|
||||
" }");
|
||||
input.setInputFiles(uploadFile);
|
||||
assertEquals("200MB.zip", input.evaluate("e => e.files[0].name"));
|
||||
assertEquals(asList("input", "change"), events.evaluate("e => e"));
|
||||
CompletableFuture<MultipartFormData> formData = new CompletableFuture<>();
|
||||
server.setRoute("/upload", exchange -> {
|
||||
try {
|
||||
MultipartFormData multipartFormData = MultipartFormData.parseRequest(exchange);
|
||||
formData.complete(multipartFormData);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
formData.completeExceptionally(e);
|
||||
}
|
||||
exchange.sendResponseHeaders(200, -1);
|
||||
});
|
||||
page.click("input[type=submit]", new Page.ClickOptions().setTimeout(90_000));
|
||||
List<MultipartFormData.Field> fields = formData.get().fields;
|
||||
assertEquals(1, fields.size());
|
||||
assertEquals("200MB.zip", fields.get(0).filename);
|
||||
assertEquals(200 * 1024 * 1024, fields.get(0).content.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWork() {
|
||||
page.setContent("<input type=file>");
|
||||
|
||||
@@ -41,7 +41,8 @@ public class TestPlaywrightCreate {
|
||||
getBrowserTypeFromEnv(playwright).launch();
|
||||
fail("Did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Looks like Playwright Test or Playwright was just installed or updated"), e.getMessage());
|
||||
assertTrue(e.getMessage().contains("Looks like Playwright Test or Playwright was just installed or updated") ||
|
||||
e.getMessage().contains("Looks like Playwright was just installed or updated."), e.getMessage());
|
||||
}
|
||||
|
||||
try (DirectoryStream<Path> ds = Files.newDirectoryStream(browsersDir)) {
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestSelectorsMisc extends TestBase {
|
||||
|
||||
@Test
|
||||
void shouldWorkWithLayoutSelectors() {
|
||||
/*
|
||||
|
||||
+--+ +--+
|
||||
| 1| | 2|
|
||||
+--+ ++-++
|
||||
| 3| | 4|
|
||||
+-------+ ++-++
|
||||
| 0 | | 5|
|
||||
| +--+ +--+--+
|
||||
| | 6| | 7|
|
||||
| +--+ +--+
|
||||
| |
|
||||
O-------+
|
||||
+--+
|
||||
| 8|
|
||||
+--++--+
|
||||
| 9|
|
||||
+--+
|
||||
|
||||
*/
|
||||
Object[][] boxes = {
|
||||
// x, y, width, height
|
||||
{0, 0, 150, 150},
|
||||
{100, 200, 50, 50},
|
||||
{200, 200, 50, 50},
|
||||
{100, 150, 50, 50},
|
||||
{201, 150, 50, 50},
|
||||
{200, 100, 50, 50},
|
||||
{50, 50, 50, 50},
|
||||
{150, 50, 50, 50},
|
||||
{150, -51, 50, 50},
|
||||
{201, -101, 50, 50},
|
||||
};
|
||||
page.setContent("<container style='width: 500px; height: 500px; position: relative;'></container>");
|
||||
page.evalOnSelector("container", "(container, boxes) => {\n" +
|
||||
" for (let i = 0; i < boxes.length; i++) {\n" +
|
||||
" const div = document.createElement('div');\n" +
|
||||
" div.style.position = 'absolute';\n" +
|
||||
" div.style.overflow = 'hidden';\n" +
|
||||
" div.style.boxSizing = 'border-box';\n" +
|
||||
" div.style.border = '1px solid black';\n" +
|
||||
" div.id = 'id' + i;\n" +
|
||||
" div.textContent = 'id' + i;\n" +
|
||||
" const box = boxes[i];\n" +
|
||||
" div.style.left = box[0] + 'px';\n" +
|
||||
" // Note that top is a flipped y coordinate.\n" +
|
||||
" div.style.top = (250 - box[1] - box[3]) + 'px';\n" +
|
||||
" div.style.width = box[2] + 'px';\n" +
|
||||
" div.style.height = box[3] + 'px';\n" +
|
||||
" container.appendChild(div);\n" +
|
||||
" const span = document.createElement('span');\n" +
|
||||
" span.textContent = '' + i;\n" +
|
||||
" div.appendChild(span);\n" +
|
||||
" }\n" +
|
||||
" }", boxes);
|
||||
|
||||
assertEquals("id7", page.evalOnSelector("div:right-of(#id6)", "e => e.id"));
|
||||
// assertEquals("id7", page.evalOnSelector("div >> right-of=\"#id6\"", "e => e.id"));
|
||||
// assertEquals("id7", page.locator("div", new Page.LocatorOptions().setRightOf(page.locator("#id6")))
|
||||
// .first().evaluate("e => e.id"));
|
||||
assertEquals("id2", page.evalOnSelector("div:right-of(#id1)", "e => e.id"));
|
||||
// assertEquals("id2", page.evalOnSelector("div >> right-of=\"#id1\"", "e => e.id"));
|
||||
assertEquals("id4", page.evalOnSelector("div:right-of(#id3)", "e => e.id"));
|
||||
// assertEquals("id4", page.evalOnSelector("div >> right-of=\"#id3\"", "e => e.id"));
|
||||
assertNull(page.querySelector("div:right-of(#id4)"));
|
||||
// assertNull(page.querySelector("div >> right-of=\"#id4\""));
|
||||
assertEquals("id7", page.evalOnSelector("div:right-of(#id0)", "e => e.id"));
|
||||
// assertEquals("id7", page.evalOnSelector("div >> right-of=\"#id0\"", "e => e.id"));
|
||||
assertEquals("id9", page.evalOnSelector("div:right-of(#id8)", "e => e.id"));
|
||||
// assertEquals("id9", page.evalOnSelector("div >> right-of=\"#id8\"", "e => e.id"));
|
||||
assertEquals("id4,id2,id5,id7,id8,id9", page.evalOnSelectorAll("div:right-of(#id3)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id4,id2,id5,id7,id8,id9", page.evalOnSelectorAll("div >> right-of=\"#id3\"", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("4,2,5,7,8,9", page.locator("div",
|
||||
// new Page.LocatorOptions().setRightOf(page.locator("#id3"))).locator("span")
|
||||
// .evaluateAll("els => els.map(e => e.textContent).join(',')"));
|
||||
assertEquals("id2,id5,id7,id8", page.evalOnSelectorAll("div:right-of(#id3, 50)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id2,id5,id7,id8", page.evalOnSelectorAll("div >> right-of=\"#id3\",50", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("2,5,7,8", page.evalOnSelectorAll("div >> right-of=\"#id3\",50 >> span", "els => els.map(e => e.textContent).join(',')"));
|
||||
// assertEquals("4,2,5,7,8,9", page.locator("div", new Page.LocatorOptions().setRightOf(page.locator("#id3")))
|
||||
// .locator("span").evaluateAll("els => els.map(e => e.textContent).join(',')"));
|
||||
assertEquals("id7,id8", page.evalOnSelectorAll("div:right-of(#id3, 49)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id7,id8", page.evalOnSelectorAll("div >> right-of=\"#id3\",49", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("7,8", page.evalOnSelectorAll("div >> right-of=\"#id3\",49 >> span", "els => els.map(e => e.textContent).join(',')"));
|
||||
// assertEquals("4,2,5,7,8,9", page.locator("div", new Page.LocatorOptions().setRightOf(page.locator("#id3")))
|
||||
// .locator("span").evaluateAll("els => els.map(e => e.textContent).join(',')"));
|
||||
|
||||
assertEquals("id1", page.evalOnSelector("div:left-of(#id2)", "e => e.id"));
|
||||
// assertEquals("id1", page.evalOnSelector("div >> left-of=\"#id2\"", "e => e.id"));
|
||||
// assertEquals("id1", page.locator("div", new Page.LocatorOptions().setLeftOf(page.locator("#id2"))).first().evaluate("e => e.id"));
|
||||
assertNull(page.querySelector("div:left-of(#id0)"));
|
||||
// assertNull(page.querySelector("div >> left-of=\"#id0\""));
|
||||
assertEquals("id0", page.evalOnSelector("div:left-of(#id5)", "e => e.id"));
|
||||
// assertEquals("id0", page.evalOnSelector("div >> left-of=\"#id5\"", "e => e.id"));
|
||||
assertEquals("id8", page.evalOnSelector("div:left-of(#id9)", "e => e.id"));
|
||||
// assertEquals("id8", page.evalOnSelector("div >> left-of=\"#id9\"", "e => e.id"));
|
||||
assertEquals("id3", page.evalOnSelector("div:left-of(#id4)", "e => e.id"));
|
||||
// assertEquals("id3", page.evalOnSelector("div >> left-of=\"#id4\"", "e => e.id"));
|
||||
assertEquals("id0,id7,id3,id1,id6,id8", page.evalOnSelectorAll("div:left-of(#id5)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id0,id7,id3,id1,id6,id8", page.evalOnSelectorAll("div >> left-of=\"#id5\"", "els => els.map(e => e.id).join(',')"));
|
||||
assertEquals("id7,id8", page.evalOnSelectorAll("div:left-of(#id5, 3)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id7,id8", page.evalOnSelectorAll("div >> left-of=\"#id5\",3", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("7,8", page.evalOnSelectorAll("div >> left-of=\"#id5\",3 >> span", "els => els.map(e => e.textContent).join(',')"));
|
||||
|
||||
assertEquals("id3", page.evalOnSelector("div:above(#id0)", "e => e.id"));
|
||||
// assertEquals("id3", page.evalOnSelector("div >> above=\"#id0\"", "e => e.id"));
|
||||
// assertEquals("id3", page.locator("div", new Page.LocatorOptions().setAbove(page.locator("#id0")))
|
||||
// .first().evaluate("e => e.id"));
|
||||
assertEquals("id4", page.evalOnSelector("div:above(#id5)", "e => e.id"));
|
||||
// assertEquals("id4", page.evalOnSelector("div >> above=\"#id5\"", "e => e.id"));
|
||||
assertEquals("id5", page.evalOnSelector("div:above(#id7)", "e => e.id"));
|
||||
// assertEquals("id5", page.evalOnSelector("div >> above=\"#id7\"", "e => e.id"));
|
||||
assertEquals("id0", page.evalOnSelector("div:above(#id8)", "e => e.id"));
|
||||
// assertEquals("id0", page.evalOnSelector("div >> above=\"#id8\"", "e => e.id"));
|
||||
assertEquals("id8", page.evalOnSelector("div:above(#id9)", "e => e.id"));
|
||||
// assertEquals("id8", page.evalOnSelector("div >> above=\"#id9\"", "e => e.id"));
|
||||
assertNull(page.querySelector("div:above(#id2)"));
|
||||
// assertNull(page.querySelector("div >> above=\"#id2\""));
|
||||
assertEquals("id4,id2,id3,id1", page.evalOnSelectorAll("div:above(#id5)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id4,id2,id3,id1", page.evalOnSelectorAll("div >> above=\"#id5\"", "els => els.map(e => e.id).join(',')"));
|
||||
assertEquals("id4,id3", page.evalOnSelectorAll("div:above(#id5, 20)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id4,id3", page.evalOnSelectorAll("div >> above=\"#id5\",20", "els => els.map(e => e.id).join(',')"));
|
||||
|
||||
assertEquals("id5", page.evalOnSelector("div:below(#id4)", "e => e.id"));
|
||||
// assertEquals("id5", page.evalOnSelector("div >> below=\"#id4\"", "e => e.id"));
|
||||
// assertEquals("id5", page.locator("div", new Page.LocatorOptions().setBelow(page.locator("#id4")))
|
||||
// .first().evaluate("e => e.id"));
|
||||
assertEquals("id0", page.evalOnSelector("div:below(#id3)", "e => e.id"));
|
||||
// assertEquals("id0", page.evalOnSelector("div >> below=\"#id3\"", "e => e.id"));
|
||||
assertEquals("id4", page.evalOnSelector("div:below(#id2)", "e => e.id"));
|
||||
// assertEquals("id4", page.evalOnSelector("div >> below=\"#id2\"", "e => e.id"));
|
||||
assertEquals("id8", page.evalOnSelector("div:below(#id6)", "e => e.id"));
|
||||
// assertEquals("id8", page.evalOnSelector("div >> below=\"#id6\"", "e => e.id"));
|
||||
assertEquals("id8", page.evalOnSelector("div:below(#id7)", "e => e.id"));
|
||||
// assertEquals("id8", page.evalOnSelector("div >> below=\"#id7\"", "e => e.id"));
|
||||
assertEquals("id9", page.evalOnSelector("div:below(#id8)", "e => e.id"));
|
||||
// assertEquals("id9", page.evalOnSelector("div >> below=\"#id8\"", "e => e.id"));
|
||||
assertNull(page.querySelector("div:below(#id9)"));
|
||||
// assertNull(page.querySelector("div >> below=\"#id9\""));
|
||||
assertEquals("id0,id5,id6,id7,id8,id9", page.evalOnSelectorAll("div:below(#id3)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id0,id5,id6,id7,id8,id9", page.evalOnSelectorAll("div >> below=\"#id3\"", "els => els.map(e => e.id).join(',')"));
|
||||
assertEquals("id0,id5,id6,id7", page.evalOnSelectorAll("div:below(#id3, 105)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id0,id5,id6,id7", page.evalOnSelectorAll("div >> below=\"#id3\" , 105", "els => els.map(e => e.id).join(',')"));
|
||||
|
||||
assertEquals("id3", page.evalOnSelector("div:near(#id0)", "e => e.id"));
|
||||
// assertEquals("id3", page.evalOnSelector("div >> near=\"#id0\"", "e => e.id"));
|
||||
// assertEquals("id3", page.locator("div", new Page.LocatorOptions().setNear(page.locator("#id0")))
|
||||
// .first().evaluate("e => e.id"));
|
||||
assertEquals("id0,id5,id3,id6", page.evalOnSelectorAll("div:near(#id7)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id0,id5,id3,id6", page.evalOnSelectorAll("div >> near=\"#id7\"", "els => els.map(e => e.id).join(',')"));
|
||||
assertEquals("id3,id6,id7,id8,id1,id5", page.evalOnSelectorAll("div:near(#id0)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id3,id6,id7,id8,id1,id5", page.evalOnSelectorAll("div >> near=\"#id0\"", "els => els.map(e => e.id).join(',')"));
|
||||
assertEquals("id0,id3,id7", page.evalOnSelectorAll("div:near(#id6)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id0,id3,id7", page.evalOnSelectorAll("div >> near=\"#id6\"", "els => els.map(e => e.id).join(',')"));
|
||||
assertEquals("id0", page.evalOnSelectorAll("div:near(#id6, 10)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id0", page.evalOnSelectorAll("div >> near=\"#id6\",10", "els => els.map(e => e.id).join(',')"));
|
||||
assertEquals("id3,id6,id7,id8,id1,id5,id4,id2", page.evalOnSelectorAll("div:near(#id0, 100)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id3,id6,id7,id8,id1,id5,id4,id2", page.evalOnSelectorAll("div >> near=\"#id0\",100", "els => els.map(e => e.id).join(',')"));
|
||||
|
||||
assertEquals("id7,id6", page.evalOnSelectorAll("div:below(#id5):above(#id8)", "els => els.map(e => e.id).join(',')"));
|
||||
// assertEquals("id7,id6", page.evalOnSelectorAll("div >> below=\"#id5\" >> above=\"#id8\"", "els => els.map(e => e.id).join(',')"));
|
||||
assertEquals("id7", page.evalOnSelector("div:below(#id5):above(#id8)", "e => e.id"));
|
||||
// assertEquals("id7", page.evalOnSelector("div >> below=\"#id5\" >> above=\"#id8\"", "e => e.id"));
|
||||
// assertEquals("id7", page.locator("div", new Page.LocatorOptions()
|
||||
// .setBelow(page.locator("#id5"))
|
||||
// .setAbove(page.locator("#id8"))).first().evaluate("e => e.id"));
|
||||
|
||||
assertEquals("id5,id6,id3", page.evalOnSelectorAll("div:right-of(#id0) + div:above(#id8)", "els => els.map(e => e.id).join(',')"));
|
||||
|
||||
try {
|
||||
ElementHandle error = page.querySelector(":near(50)");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("\"near\" engine expects a selector list and optional maximum distance in pixels"), e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
ElementHandle error1 = page.querySelector("div >> left-of=abc");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Malformed selector: left-of=abc"));
|
||||
}
|
||||
|
||||
try {
|
||||
ElementHandle error2 = page.querySelector("left-of=\"div\"");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("\"left-of\" selector cannot be first"), e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
ElementHandle error3 = page.querySelector("div >> left-of=33");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Malformed selector: left-of=33"));
|
||||
}
|
||||
|
||||
try {
|
||||
ElementHandle error4 = page.querySelector("div >> left-of='span','foo'");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Malformed selector: left-of='span','foo'"));
|
||||
}
|
||||
|
||||
try {
|
||||
ElementHandle error5 = page.querySelector("div >> left-of='span',3,4");
|
||||
fail("did not throw");
|
||||
} catch (PlaywrightException e) {
|
||||
assertTrue(e.getMessage().contains("Malformed selector: left-of='span',3,4"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,6 @@ public class TestTracing extends TestBase {
|
||||
|
||||
@BeforeAll
|
||||
void launchBrowser(@TempDir Path tempDir) {
|
||||
System.out.println("new launchBrowser(");
|
||||
BrowserType.LaunchOptions options = createLaunchOptions();
|
||||
options.setTracesDir(tempDir.resolve("trace-dir"));
|
||||
launchBrowser(options);
|
||||
@@ -126,4 +125,10 @@ public class TestTracing extends TestBase {
|
||||
assertEquals(new String(thisFile, UTF_8), new String(sources.values().iterator().next(), UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotFailWhenSourcesSetExplicitlyToFalse() throws IOException {
|
||||
Assumptions.assumeTrue(System.getenv("PLAYWRIGHT_JAVA_SRC") == null, "PLAYWRIGHT_JAVA_SRC must not be set for this test");
|
||||
context.tracing().start(new Tracing.StartOptions().setSources(false));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,24 @@ import static com.microsoft.playwright.Utils.mapOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class TestWheel extends TestBase {
|
||||
private void expectEvent(Map<String, Object> expected) {
|
||||
// Chromium reports deltaX/deltaY scaled by host device scale factor.
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1324819
|
||||
// https://github.com/microsoft/playwright/issues/7362
|
||||
// Different bots have different scale factors (usually 1 or 2), so we just ignore the values
|
||||
// instead of guessing the host scale factor.
|
||||
// This first appeared in Chromium 102.
|
||||
boolean ignoreDelta = isChromium() && isMac;
|
||||
Map<String, Object> received = (Map<String, Object>) page.evaluate("window.lastEvent");
|
||||
if (ignoreDelta) {
|
||||
expected.remove("deltaX");
|
||||
expected.remove("deltaY");
|
||||
received.remove("deltaX");
|
||||
received.remove("deltaY");
|
||||
}
|
||||
assertEquals(expected, received);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDispatchWheelEvents() {
|
||||
page.setContent("<div style='width: 5000px; height: 5000px;'></div>");
|
||||
@@ -42,7 +60,7 @@ public class TestWheel extends TestBase {
|
||||
"altKey", false,
|
||||
"metaKey", false);
|
||||
page.waitForFunction("window.scrollY === 100");
|
||||
assertEquals(expected, page.evaluate("window.lastEvent"));
|
||||
expectEvent(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -71,7 +89,7 @@ public class TestWheel extends TestBase {
|
||||
"shiftKey", true,
|
||||
"altKey", false,
|
||||
"metaKey", false);
|
||||
assertEquals(expected, page.evaluate("window.lastEvent"));
|
||||
expectEvent(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -90,7 +108,7 @@ public class TestWheel extends TestBase {
|
||||
"shiftKey", false,
|
||||
"altKey", false,
|
||||
"metaKey", false);
|
||||
assertEquals(expected, page.evaluate("window.lastEvent"));
|
||||
expectEvent(expected);
|
||||
page.waitForFunction("window.scrollX === 100");
|
||||
}
|
||||
|
||||
@@ -113,7 +131,7 @@ public class TestWheel extends TestBase {
|
||||
"shiftKey", false,
|
||||
"altKey", false,
|
||||
"metaKey", false);
|
||||
assertEquals(expected, page.evaluate("window.lastEvent"));
|
||||
expectEvent(expected);
|
||||
// give the page a chacne to scroll
|
||||
page.waitForTimeout(100);
|
||||
// ensure that it did not.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE HTML>
|
||||
<style>
|
||||
div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
background-color: red;
|
||||
transition: all 10s;
|
||||
}
|
||||
.transition {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
<script>
|
||||
window.addEventListener('load', () => {
|
||||
document.querySelector('div').classList.add('transition');
|
||||
}, false);
|
||||
</script>
|
||||
@@ -4,8 +4,8 @@
|
||||
<title>File upload test</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/input/fileupload.html">
|
||||
<input type="file">
|
||||
<form action="/upload" method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="file1">
|
||||
<input type="submit">
|
||||
</form>
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<html>
|
||||
<head>
|
||||
<style type="text/css">
|
||||
div::after {
|
||||
position: absolute;
|
||||
content: " ";
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
background-color: red;
|
||||
animation-name: z-spin;
|
||||
animation-duration: 5s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
div {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
@keyframes z-spin {
|
||||
0% { transform: rotateZ(0deg); }
|
||||
100% { transform: rotateZ(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<div class="square"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,31 @@
|
||||
<html>
|
||||
<head></head>
|
||||
<body></body>
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const shadow = document.body.attachShadow({mode: 'open'});
|
||||
shadow.append(document.createElement('div'));
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
background-color: red;
|
||||
animation-name: z-spin;
|
||||
animation-duration: 5s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
@keyframes z-spin {
|
||||
0% { transform: rotateZ(0deg); }
|
||||
100% { transform: rotateZ(360deg); }
|
||||
}
|
||||
`;
|
||||
shadow.append(style);
|
||||
}, false);
|
||||
</script>
|
||||
</html>
|
||||
@@ -13,7 +13,7 @@
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
|
||||
@keyframes z-spin {
|
||||
0% { transform: rotateZ(0deg); }
|
||||
100% { transform: rotateZ(360deg); }
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE HTML>
|
||||
<style>
|
||||
div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
background-color: red;
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
<script>
|
||||
document.querySelector('div').animate(
|
||||
[
|
||||
{ transform: 'rotate(0deg)' },
|
||||
{ transform: 'rotate(360deg)' }
|
||||
], {
|
||||
duration: 3000,
|
||||
iterations: Infinity
|
||||
}
|
||||
);
|
||||
</script>
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>parent-pom</artifactId>
|
||||
<version>1.19.0-SNAPSHOT</version>
|
||||
<version>1.22.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Playwright Parent Project</name>
|
||||
<description>Java library to automate Chromium, Firefox and WebKit with a single API.
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.19.0
|
||||
1.22.0
|
||||
|
||||
@@ -33,7 +33,7 @@ fi
|
||||
mkdir -p driver
|
||||
cd driver
|
||||
|
||||
for PLATFORM in mac linux win32_x64
|
||||
for PLATFORM in mac linux linux-arm64 win32_x64
|
||||
do
|
||||
FILE_NAME=$FILE_PREFIX-$PLATFORM.zip
|
||||
if [[ -d $PLATFORM ]]; then
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "-- tagging: $2"
|
||||
docker tag $1 $2
|
||||
docker push $2
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>api-generator</artifactId>
|
||||
<version>1.19.0-SNAPSHOT</version>
|
||||
<version>1.22.0</version>
|
||||
<name>Playwright - API Generator</name>
|
||||
<description>
|
||||
This is an internal module used to generate Java API from the upstream Playwright
|
||||
|
||||
@@ -181,10 +181,15 @@ abstract class Element {
|
||||
int start = 0;
|
||||
while (matcher.find()) {
|
||||
String url = matcher.group(2);
|
||||
if (url.startsWith("../")) {
|
||||
// ../actionability.md#editable => https://playwright.dev/java/docs/actionability/#editable
|
||||
url = url.replace(".md", "");
|
||||
url = url.replace("../", "https://playwright.dev/java/docs/");
|
||||
}
|
||||
if (url.startsWith("./")) {
|
||||
// ./actionability.md#editable => https://playwright.dev/java/docs/actionability/#editable
|
||||
url = url.replace(".md", "/");
|
||||
url = url.replace("./", "https://playwright.dev/java/docs/");
|
||||
// ./class-tracing.md => https://playwright.dev/java/docs/api/class-tracing
|
||||
url = url.replace(".md", "");
|
||||
url = url.replace("./", "https://playwright.dev/java/docs/api/");
|
||||
}
|
||||
String link = "<a href=\"" + url + "\">" + matcher.group(1) + "</a>";
|
||||
linkified += paragraph.substring(start, matcher.start());
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>test-cli-version</artifactId>
|
||||
<version>1.19.0-SNAPSHOT</version>
|
||||
<version>1.22.0</version>
|
||||
<name>Test Playwright Command Line Version</name>
|
||||
<properties>
|
||||
<compiler.version>1.8</compiler.version>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>test-local-installation</artifactId>
|
||||
<version>1.19.0-SNAPSHOT</version>
|
||||
<version>1.22.0</version>
|
||||
<name>Test local installation</name>
|
||||
<description>Runs Playwright test suite (copied from playwright module) against locally cached Playwright</description>
|
||||
<properties>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</parent>
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>test-spring-boot-starter</artifactId>
|
||||
<version>1.19.0-SNAPSHOT</version>
|
||||
<version>1.22.0</version>
|
||||
<name>Test Playwright With Spring Boot</name>
|
||||
<properties>
|
||||
<spring.version>2.4.3</spring.version>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.microsoft.playwright</groupId>
|
||||
<artifactId>update-version</artifactId>
|
||||
<version>1.19.0-SNAPSHOT</version>
|
||||
<version>1.22.0</version>
|
||||
<name>Playwright - Update Version in Documentation</name>
|
||||
<description>
|
||||
This is an internal module used to update versions in the documentation based on
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
FROM ubuntu:focal
|
||||
|
||||
ARG DOCKER_IMAGE_NAME_TEMPLATE="mcr.microsoft.com/playwright/java:v%version%-focal"
|
||||
|
||||
# === INSTALL JDK and Maven ===
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
openjdk-11-jdk maven \
|
||||
# Install utilities required for downloading browsers
|
||||
curl \
|
||||
# Install utilities required for downloading driver
|
||||
unzip && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
# Create the pwuser
|
||||
adduser pwuser
|
||||
|
||||
ARG PW_TARGET_ARCH
|
||||
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk-${PW_TARGET_ARCH}
|
||||
|
||||
# === BAKE BROWSERS INTO IMAGE ===
|
||||
|
||||
# Browsers will remain downloaded in `/ms-playwright`.
|
||||
# Note: make sure to set 777 to the registry so that any user can access
|
||||
# registry.
|
||||
|
||||
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
|
||||
|
||||
RUN mkdir /ms-playwright && \
|
||||
mkdir /tmp/pw-java
|
||||
|
||||
COPY . /tmp/pw-java
|
||||
|
||||
RUN cd /tmp/pw-java && \
|
||||
./scripts/download_driver_for_all_platforms.sh && \
|
||||
mvn install -D skipTests --no-transfer-progress && \
|
||||
DEBIAN_FRONTEND=noninteractive mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
|
||||
-D exec.args="install-deps" -f playwright/pom.xml --no-transfer-progress && \
|
||||
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
|
||||
-D exec.args="install" -f playwright/pom.xml --no-transfer-progress && \
|
||||
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
|
||||
-D exec.args="mark-docker-image '${DOCKER_IMAGE_NAME_TEMPLATE}'" -f playwright/pom.xml --no-transfer-progress && \
|
||||
rm -rf /tmp/pw-java && \
|
||||
chmod -R 777 $PLAYWRIGHT_BROWSERS_PATH
|
||||
@@ -0,0 +1,4 @@
|
||||
# Docker build infra
|
||||
|
||||
This folder contains the infra to build the Docker images for Java. It's based on
|
||||
the upstream code located in [`microsoft/playwright/utils/docker`](https://github.com/microsoft/playwright/tree/main/utils/docker).
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
set +x
|
||||
|
||||
if [[ ($1 == '--help') || ($1 == '-h') || ($1 == '') || ($2 == '') ]]; then
|
||||
echo "usage: $(basename $0) {--arm64,--amd64} {focal} playwright:localbuild-focal"
|
||||
echo
|
||||
echo "Build Playwright docker image and tag it as 'playwright:localbuild-focal'."
|
||||
echo "Once image is built, you can run it with"
|
||||
echo ""
|
||||
echo " docker run --rm -it playwright:localbuild-focal /bin/bash"
|
||||
echo ""
|
||||
echo "NOTE: this requires on Playwright PIP dependencies to be installed"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
function cleanup() {
|
||||
:
|
||||
}
|
||||
|
||||
trap "cleanup; cd $(pwd -P)" EXIT
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
PLATFORM=""
|
||||
if [[ "$1" == "--arm64" ]]; then
|
||||
PLATFORM="linux/arm64";
|
||||
elif [[ "$1" == "--amd64" ]]; then
|
||||
PLATFORM="linux/amd64"
|
||||
else
|
||||
echo "ERROR: unknown platform specifier - $1. Only --arm64 or --amd64 is supported"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PW_TARGET_ARCH=$(echo $1 | cut -c3-)
|
||||
|
||||
docker build --platform "${PLATFORM}" --build-arg "PW_TARGET_ARCH=${PW_TARGET_ARCH}" -t "$3" -f "Dockerfile.$2" ../../
|
||||
Executable
+121
@@ -0,0 +1,121 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set +x
|
||||
|
||||
trap "cd $(pwd -P)" EXIT
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
MCR_IMAGE_NAME="playwright/java"
|
||||
|
||||
POM_FILE=../../pom.xml
|
||||
if [[ ! -f ${POM_FILE} ]]; then
|
||||
echo "ERROR: pom.xml not found at ${POM_FILE}"
|
||||
exit 1;
|
||||
fi
|
||||
PW_VERSION=$(mvn exec:exec -Dexec.executable='echo' -Dexec.args='${project.version}' -f ${POM_FILE} --non-recursive -q 2>/dev/null)
|
||||
|
||||
RELEASE_CHANNEL="$1"
|
||||
if [[ "${RELEASE_CHANNEL}" == "stable" ]]; then
|
||||
if [[ ! "${PW_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "ERROR: cannot publish stable docker with Playwright version '${PW_VERSION}'"
|
||||
exit 1
|
||||
fi
|
||||
elif [[ "${RELEASE_CHANNEL}" == "canary" ]]; then
|
||||
:
|
||||
else
|
||||
echo "ERROR: unknown release channel - ${RELEASE_CHANNEL}"
|
||||
echo "Must be either 'stable' or 'canary'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${GITHUB_SHA}" ]]; then
|
||||
echo "ERROR: GITHUB_SHA env variable must be specified"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BIONIC_TAGS=(
|
||||
"next-bionic"
|
||||
"v${PW_VERSION}-bionic"
|
||||
)
|
||||
if [[ "$RELEASE_CHANNEL" == "stable" ]]; then
|
||||
BIONIC_TAGS+=("bionic")
|
||||
fi
|
||||
|
||||
FOCAL_TAGS=(
|
||||
"next"
|
||||
"sha-${GITHUB_SHA}"
|
||||
"next-focal"
|
||||
)
|
||||
|
||||
if [[ "$RELEASE_CHANNEL" == "stable" ]]; then
|
||||
FOCAL_TAGS+=("latest")
|
||||
FOCAL_TAGS+=("focal")
|
||||
FOCAL_TAGS+=("v${PW_VERSION}-focal")
|
||||
FOCAL_TAGS+=("v${PW_VERSION}")
|
||||
fi
|
||||
|
||||
tag_and_push() {
|
||||
local source="$1"
|
||||
local target="$2"
|
||||
echo "-- tagging: $target"
|
||||
docker tag $source $target
|
||||
docker push $target
|
||||
}
|
||||
|
||||
publish_docker_images_with_arch_suffix() {
|
||||
local FLAVOR="$1"
|
||||
local TAGS=()
|
||||
if [[ "$FLAVOR" == "bionic" ]]; then
|
||||
TAGS=("${BIONIC_TAGS[@]}")
|
||||
elif [[ "$FLAVOR" == "focal" ]]; then
|
||||
TAGS=("${FOCAL_TAGS[@]}")
|
||||
else
|
||||
echo "ERROR: unknown flavor - $FLAVOR. Must be either 'bionic' or 'focal'"
|
||||
exit 1
|
||||
fi
|
||||
local ARCH="$2"
|
||||
if [[ "$ARCH" != "amd64" && "$ARCH" != "arm64" ]]; then
|
||||
echo "ERROR: unknown arch - $ARCH. Must be either 'amd64' or 'arm64'"
|
||||
exit 1
|
||||
fi
|
||||
# Prune docker images to avoid platform conflicts
|
||||
docker system prune -fa
|
||||
./build.sh "--${ARCH}" "${FLAVOR}" "${MCR_IMAGE_NAME}:localbuild"
|
||||
|
||||
for ((i = 0; i < ${#TAGS[@]}; i++)) do
|
||||
local TAG="${TAGS[$i]}"
|
||||
tag_and_push "${MCR_IMAGE_NAME}:localbuild" "playwright.azurecr.io/public/${MCR_IMAGE_NAME}:${TAG}-${ARCH}"
|
||||
done
|
||||
}
|
||||
|
||||
publish_docker_manifest () {
|
||||
local FLAVOR="$1"
|
||||
local TAGS=()
|
||||
if [[ "$FLAVOR" == "bionic" ]]; then
|
||||
TAGS=("${BIONIC_TAGS[@]}")
|
||||
elif [[ "$FLAVOR" == "focal" ]]; then
|
||||
TAGS=("${FOCAL_TAGS[@]}")
|
||||
else
|
||||
echo "ERROR: unknown flavor - $FLAVOR. Must be either 'bionic' or 'focal'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for ((i = 0; i < ${#TAGS[@]}; i++)) do
|
||||
local TAG="${TAGS[$i]}"
|
||||
local BASE_IMAGE_TAG="playwright.azurecr.io/public/${MCR_IMAGE_NAME}:${TAG}"
|
||||
local IMAGE_NAMES=""
|
||||
if [[ "$2" == "arm64" || "$2" == "amd64" ]]; then
|
||||
IMAGE_NAMES="${IMAGE_NAMES} ${BASE_IMAGE_TAG}-$2"
|
||||
fi
|
||||
if [[ "$3" == "arm64" || "$3" == "amd64" ]]; then
|
||||
IMAGE_NAMES="${IMAGE_NAMES} ${BASE_IMAGE_TAG}-$3"
|
||||
fi
|
||||
docker manifest create "${BASE_IMAGE_TAG}" $IMAGE_NAMES
|
||||
docker manifest push "${BASE_IMAGE_TAG}"
|
||||
done
|
||||
}
|
||||
|
||||
publish_docker_images_with_arch_suffix focal amd64
|
||||
publish_docker_images_with_arch_suffix focal arm64
|
||||
publish_docker_manifest focal amd64 arm64
|
||||
Reference in New Issue
Block a user