1
0
mirror of synced 2026-05-23 11:13:15 +00:00

Compare commits

..

4 Commits

Author SHA1 Message Date
Yury Semikhatsky 0a4159f15e chore: update released version to 0.181.0 2021-02-19 09:38:58 -08:00
Yury Semikhatsky b2c00a2195 fix: update worflow configs to run on release branches (#288) 2021-02-19 09:31:36 -08:00
Yury Semikhatsky 98195b762b chore: roll 1.8.1 driver in the release branch (#287) 2021-02-19 09:22:54 -08:00
Yury Semikhatsky b9dbd56765 chore: set version to 0.180.0 (#227) 2021-01-22 17:56:03 -08:00
292 changed files with 7680 additions and 34643 deletions
-41
View File
@@ -1,41 +0,0 @@
---
name: Bug Report
about: Something doesn't work like it should? Tell us!
title: "[BUG]"
labels: ''
assignees: ''
---
**Context:**
- Playwright Version: [what Playwright version do you use?]
- Operating System: [e.g. Windows, Linux or Mac]
- Browser: [e.g. All, Chromium, Firefox, WebKit]
- Extra: [any specific details about your environment]
<!-- CLI to auto-capture this info -->
<!-- npx envinfo --preset playwright --markdown -->
**Code Snippet**
Help us help you! Put down a short code snippet that illustrates your bug and
that we can run and debug locally. For example:
```java
import com.microsoft.playwright.*;
public class ExampleReproducible {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
// ...
}
}
}
```
**Describe the bug**
Add any other details about the problem here.
-4
View File
@@ -1,4 +0,0 @@
contact_links:
- name: Join our Slack community
url: https://aka.ms/playwright-slack
about: Ask questions and discuss with other community members
-11
View File
@@ -1,11 +0,0 @@
---
name: Feature request
about: Request new features to be added
title: "[Feature]"
labels: ''
assignees: ''
---
Let us know what functionality you'd like to see in Playwright and what your use case is.
Do you think others might benefit from this as well?
-10
View File
@@ -1,10 +0,0 @@
---
name: I have a question
about: Feel free to ask us your questions!
title: "[Question]"
labels: ''
assignees: ''
---
-38
View File
@@ -1,38 +0,0 @@
---
name: Report regression
about: Functionality that used to work and does not any more
title: "[REGRESSION]: "
labels: ''
assignees: ''
---
**Context:**
- GOOD Playwright Version: [what Playwright version worked nicely?]
- BAD Playwright Version: [what Playwright version doesn't work any more?]
- Operating System: [e.g. Windows, Linux or Mac]
- Extra: [any specific details about your environment]
**Code Snippet**
Help us help you! Put down a short code snippet that illustrates your bug and
that we can run and debug locally. For example:
```java
import com.microsoft.playwright.*;
public class ExampleReproducible {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
// ...
}
}
}
```
**Describe the bug**
Add any other details about the problem here.
+4 -6
View File
@@ -1,17 +1,15 @@
name: Publish
on:
release:
types: [published]
push:
branches:
- main
workflow_dispatch
jobs:
build:
timeout-minutes: 30
strategy:
fail-fast: false
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 1.8
uses: actions/setup-java@v1
with:
@@ -1,26 +0,0 @@
name: "devrelease:docker"
on:
push:
branches:
- main
jobs:
publish-canary-docker:
name: "publish to DockerHub"
runs-on: ubuntu-20.04
if: github.repository == 'microsoft/playwright-java'
steps:
- uses: actions/checkout@v2
- uses: azure/docker-login@v1
with:
login-server: playwright.azurecr.io
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 }}
@@ -1,36 +0,0 @@
name: Publish Release Docker
on:
release:
types: [published]
workflow_dispatch:
branches:
- release-*
jobs:
publish-canary-docker:
name: publish to DockerHub
runs-on: ubuntu-20.04
if: github.repository == 'microsoft/playwright-java'
steps:
- uses: actions/checkout@v2
- uses: azure/docker-login@v1
with:
login-server: playwright.azurecr.io
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: |
# 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
+10 -105
View File
@@ -1,15 +1,17 @@
name: Build & Test
name: Test
on:
push:
branches:
- main
- master
- release-*
pull_request:
branches:
- main
- master
- release-*
jobs:
dev:
build:
timeout-minutes: 30
strategy:
fail-fast: false
@@ -19,12 +21,11 @@ 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
uses: actions/setup-java@v1
with:
distribution: zulu
java-version: 8
java-version: 1.8
- name: Cache Maven packages
uses: actions/cache@v2
with:
@@ -37,102 +38,6 @@ jobs:
- name: Build with Maven
run: mvn -B package -D skipTests --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end
run: mvn test --no-transfer-progress
env:
BROWSER: ${{ matrix.browser }}
- name: Run tracing tests w/ sources
run: mvn test --no-transfer-progress --fail-at-end -D test=*TestTracing*
env:
BROWSER: ${{ matrix.browser }}
PLAYWRIGHT_JAVA_SRC: src/test/java
- 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
stable:
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
browser-channel: [chrome]
include:
- os: windows-latest
browser-channel: msedge
- os: macos-latest
browser-channel: msedge
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1.5.0
- name: Install Media Pack
if: matrix.os == 'windows-latest'
shell: powershell
run: Install-WindowsFeature Server-Media-Foundation
- 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: Run tests
run: mvn test --no-transfer-progress --fail-at-end
env:
BROWSER: chromium
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
Java_17:
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
browser: [chromium, firefox, webkit]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1.5.0
- 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: Run tests
run: mvn test --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
-31
View File
@@ -1,31 +0,0 @@
name: Test CLI
on:
push:
branches:
- main
- release-*
pull_request:
branches:
- main
- release-*
jobs:
verify:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- 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
run: scripts/download_driver_for_all_platforms.sh
- name: Intall Playwright
run: mvn install -D skipTests --no-transfer-progress
- name: Test CLI
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -f playwright/pom.xml -D exec.args=-V
- name: Test CLI version
shell: bash
run: tools/test-cli-version/test.sh
-30
View File
@@ -1,30 +0,0 @@
name: Test Docker
on:
push:
paths:
- '.github/workflows/test_docker.yml'
- 'Dockerfile*'
branches:
- main
- release-*
pull_request:
paths:
- .github/workflows/test_docker.yml
- Dockerfile.*
- scripts/CLI_VERSION
- '**/pom.xml'
branches:
- main
- release-*
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Build Docker image
run: docker build -t playwright-java:localbuild-focal -f Dockerfile.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)"
docker exec "${CONTAINER_ID}" /root/playwright/tools/test-local-installation/create_project_and_run_tests.sh
+5 -6
View File
@@ -2,14 +2,14 @@ name: Verify API
on:
push:
branches:
- main
- master
- release-*
paths:
- 'scripts/*'
- 'api-generator/*'
pull_request:
branches:
- main
- master
- release-*
paths:
- 'scripts/**'
@@ -17,10 +17,11 @@ on:
jobs:
verify:
timeout-minutes: 30
strategy:
fail-fast: true
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:
@@ -31,12 +32,10 @@ jobs:
run: scripts/download_driver_for_all_platforms.sh
- name: Regenerate APIs
run: scripts/generate_api.sh
- name: Update browser versions in README
run: scripts/update_readme.sh
- name: Verify API is up to date
run: |
if [[ -n $(git status -s) ]]; then
echo "ERROR: generated interfaces/docs differ from the current sources:"
echo "ERROR: generated interfaces differ from the current sources:"
git diff
exit 1
fi
+2 -15
View File
@@ -2,16 +2,7 @@
## How to Contribute
### Installing Developer Tools
Install git, Java JDK (version >= 8), Maven (tested with version 3.6.3), on Ubuntu 20.04
just run the following command:
```sh
sudo apt-get install git openjdk-11-jdk maven
```
### Getting the Code
### Getting Code
1. Clone this repository
@@ -28,15 +19,11 @@ scripts/download_driver_for_all_platforms.sh
Names of published driver archives can be found at https://github.com/microsoft/playwright-cli/actions
### Building and running the tests with Maven
### Compiling and running the tests with Maven
```bash
mvn compile
mvn test
# Executing a single test
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize
# Executing a single test class
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes
```
### Generating API
+56 -17
View File
@@ -1,32 +1,71 @@
FROM ubuntu:focal
# === INSTALL BROWSER DEPENDENCIES ===
# Install WebKit dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libwoff1 \
libopus0 \
libwebp6 \
libwebpdemux2 \
libenchant1c2a \
libgudev-1.0-0 \
libsecret-1-0 \
libhyphen0 \
libgdk-pixbuf2.0-0 \
libegl1 \
libnotify4 \
libxslt1.1 \
libevent-2.1-7 \
libgles2 \
libxcomposite1 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libepoxy0 \
libgtk-3-0 \
libharfbuzz-icu0
# Install gstreamer and plugins to support video playback in WebKit.
RUN apt-get update && apt-get install -y --no-install-recommends \
libgstreamer-gl1.0-0 \
libgstreamer-plugins-bad1.0-0 \
gstreamer1.0-plugins-good \
gstreamer1.0-libav
# Install Chromium dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libnss3 \
libxss1 \
libasound2 \
fonts-noto-color-emoji \
libxtst6
# Install Firefox dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libdbus-glib-1-2 \
libxt6
# Install ffmpeg to bring in audio and video codecs necessary for playing videos in Firefox.
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg
# (Optional) Install XVFB if there's a need to run browsers in headful mode
RUN apt-get update && apt-get install -y --no-install-recommends \
xvfb
# === 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
openjdk-8-jdk maven
# 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
# === INSTALL Playwright-java ===
RUN mkdir /tmp/pw-java
COPY . /tmp/pw-java
RUN cd /tmp/pw-java && \
./scripts/download_driver_for_all_platforms.sh && \
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
+67 -69
View File
@@ -1,21 +1,18 @@
# 🎭 [Playwright](https://playwright.dev) for Java
[![javadoc](https://javadoc.io/badge2/com.microsoft.playwright/playwright/javadoc.svg)](https://javadoc.io/doc/com.microsoft.playwright/playwright)
[![maven version](https://img.shields.io/maven-central/v/com.microsoft.playwright/playwright)](https://search.maven.org/search?q=com.microsoft.playwright)
[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/https/oss.sonatype.org/com.microsoft.playwright/playwright.svg)](https://oss.sonatype.org/content/repositories/snapshots/com/microsoft/playwright/playwright/)
[![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://aka.ms/playwright-slack)
[![maven version](https://img.shields.io/maven-central/v/com.microsoft.playwright/playwright)](https://search.maven.org/search?q=com.microsoft.playwright) [![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://join.slack.com/t/playwright/shared_invite/enQtOTEyMTUxMzgxMjIwLThjMDUxZmIyNTRiMTJjNjIyMzdmZDA3MTQxZWUwZTFjZjQwNGYxZGM5MzRmNzZlMWI5ZWUyOTkzMjE5Njg1NDg)
#### [Website](https://playwright.dev/java/) | [API reference](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html)
#### [Website](https://playwright.dev/) | [API reference](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html)
Playwright is a Java library to automate [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) with a single API. Playwright is built to enable cross-browser web automation that is **ever-green**, **capable**, **reliable** and **fast**.
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->100.0.4863.0<!-- 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: |
| Chromium <!-- GEN:chromium-version -->89.0.4344.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->14.1<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->85.0b1<!-- 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.
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/#?path=docs/intro.md&q=system-requirements) for details.
* [Usage](#usage)
- [Add Maven dependency](#add-maven-dependency)
@@ -35,7 +32,7 @@ Playwright requires **Java 8** or newer.
#### Add Maven dependency
Playwright is distributed as a set of [Maven](https://maven.apache.org/what-is-maven.html) modules. The easiest way to use it is to add one dependency to your Maven `pom.xml` file as described below. If you're not familiar with Maven please refer to its [documentation](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html).
Playwright is distributed as a set of [Maven](https://maven.apache.org/what-is-maven.html) modules. The easiest way to use it is to add a couple of dependencies to your Maven `pom.xml` file as described below. If you're not familiar with Maven please refer to its [documentation](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html).
To run Playwright simply add following dependency to your Maven project:
@@ -43,13 +40,13 @@ To run Playwright simply add following dependency to your Maven project:
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.17.0</version>
<version>0.171.0</version>
</dependency>
```
#### Is Playwright thread-safe?
No, Playwright is not thread safe, i.e. all its methods as well as methods on all objects created by it (such as BrowserContext, Browser, Page etc.) are expected to be called on the same thread where Playwright object was created or proper synchronization should be implemented to ensure only one thread calls Playwright methods at any given time. Having said that it's okay to create multiple Playwright instances each on its own thread.
No, Playwright is not thread safe, i.e. all its methods as well as methods on all objects created by it (such as BrowserContext, Browser, Page etc.) are expected to be called on the same thread where Playwright object was created or proper synchronization should be implemented to ensure only one thread calls Playwright methods at any given time. Having said that it's okay to create new many Playwright instances each on its own thread.
## Examples
@@ -67,22 +64,23 @@ import java.util.Arrays;
import java.util.List;
public class PageScreenshot {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
List<BrowserType> browserTypes = Arrays.asList(
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
List<BrowserType> browserTypes = Arrays.asList(
playwright.chromium(),
playwright.webkit(),
playwright.firefox()
);
for (BrowserType browserType : browserTypes) {
try (Browser browser = browserType.launch()) {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("screenshot-" + browserType.name() + ".png")));
}
}
);
for (BrowserType browserType : browserTypes) {
Browser browser = browserType.launch();
BrowserContext context = browser.newContext(
new Browser.NewContextOptions().withViewport(800, 600));
Page page = context.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("screenshot-" + browserType.name() + ".png")));
browser.close();
}
playwright.close();
}
}
```
@@ -92,31 +90,27 @@ public class PageScreenshot {
This snippet emulates Mobile Chromium on a device at a given geolocation, navigates to openstreetmap.org, performs action and takes a screenshot.
```java
import com.microsoft.playwright.options.*;
import com.microsoft.playwright.*;
import java.nio.file.Paths;
import static java.util.Arrays.asList;
public class MobileAndGeolocation {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36")
.setViewportSize(411, 731)
.setDeviceScaleFactor(2.625)
.setIsMobile(true)
.setHasTouch(true)
.setLocale("en-US")
.setGeolocation(41.889938, 12.492507)
.setPermissions(asList("geolocation")));
Page page = context.newPage();
page.navigate("https://www.openstreetmap.org/");
page.click("a[data-original-title=\"Show My Location\"]");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("colosseum-pixel2.png")));
}
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.chromium();
Browser browser = browserType.launch(new BrowserType.LaunchOptions().withHeadless(false));
DeviceDescriptor pixel2 = playwright.devices().get("Pixel 2");
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.withDevice(pixel2)
.withLocale("en-US")
.withGeolocation(new Geolocation(41.889938, 12.492507))
.withPermissions(asList("geolocation")));
Page page = context.newPage();
page.navigate("https://www.openstreetmap.org/");
page.click("a[data-original-title=\"Show My Location\"]");
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("colosseum-pixel2.png")));
browser.close();
playwright.close();
}
}
```
@@ -129,21 +123,23 @@ This code snippet navigates to example.com in Firefox, and executes a script in
import com.microsoft.playwright.*;
public class EvaluateInBrowserContext {
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://www.example.com/");
Object dimensions = page.evaluate("() => {\n" +
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.firefox();
Browser browser = browserType.launch(new BrowserType.LaunchOptions().withHeadless(false));
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://www.example.com/");
Object dimensions = page.evaluate("() => {\n" +
" return {\n" +
" width: document.documentElement.clientWidth,\n" +
" height: document.documentElement.clientHeight,\n" +
" deviceScaleFactor: window.devicePixelRatio\n" +
" }\n" +
"}");
System.out.println(dimensions);
}
System.out.println(dimensions);
browser.close();
playwright.close();
}
}
```
@@ -156,31 +152,33 @@ This code snippet sets up request routing for a WebKit page to log all network r
import com.microsoft.playwright.*;
public class InterceptNetworkRequests {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.webkit().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.route("**", route -> {
System.out.println(route.request().url());
route.resume();
});
page.navigate("http://todomvc.com");
}
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.webkit();
Browser browser = browserType.launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.route("**", route -> {
System.out.println(route.request().url());
route.continue_();
});
page.navigate("http://todomvc.com");
browser.close();
playwright.close();
}
}
```
## Documentation
Check out our [new documentation site](https://playwright.dev/java)!.
You can also browse [javadoc online](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html).
We are in the process of converting our documentation from the Node.js form to [Javadocs](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html). You can go ahead and use the Node.js [documentation](https://playwright.dev/) since the API is pretty much the same.
## Contributing
Follow [the instructions](https://github.com/microsoft/playwright-java/blob/main/CONTRIBUTING.md#getting-code) to build the project from source and install the driver.
Follow [the instructions](https://github.com/microsoft/playwright-java/blob/master/CONTRIBUTING.md#getting-code) to build the project from source and install the driver.
## Is Playwright for Java ready?
Yes, Playwright for Java is ready. v1.10.0 is the first stable release. Going forward we will adhere to [semantic versioning](https://semver.org/) of the API.
Yes, Playwright for Java is ready. We are still not at the version v1.0, so breaking API changes could potentially happen. But a) this is unlikely and b) we will only do that if we know it improves your experience with the new library. We'd like to collect your feedback before we freeze the API for v1.0.
> Note: We don't yet support some of the edge-cases of the vendor-specific APIs such as collecting Chromium trace, coverage report, etc.
-13
View File
@@ -1,13 +0,0 @@
# Rolling Playwright-Java to the latest Playwright driver
* make sure to have at least Java 8 and Maven 3.6.3
* clone playwright for java: http://github.com/microsoft/playwright-java
* set new driver version in `scripts/CLI_VERSION`
* regenerate API: `./scripts/download_driver_for_all_platforms.sh -f && ./scripts/generate_api.sh && ./scripts/update_readme.sh`
* commit & send PR with the roll
# Updating Version
```bash
./scripts/set_maven_version.sh 1.15.0
```
+15 -2
View File
@@ -6,13 +6,13 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.19.0</version>
<version>0.181.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
<name>Playwright - Drivers For All Platforms</name>
<description>
This module includes Playwright driver and related utilities for all supported platforms.
This module includes playwright-cli binary and related utilities for all supported platforms.
It is intended to be used on the systems where Playwright driver is not preinstalled.
</description>
@@ -28,10 +28,23 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<source>8</source>
<failOnError>false</failOnError>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
</plugins>
</build>
@@ -21,47 +21,25 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class DriverJar extends Driver {
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
private final Path driverTempDir;
public DriverJar() throws IOException {
// Allow specifying custom path for the driver installation
// See https://github.com/microsoft/playwright-java/issues/728
String alternativeTmpdir = System.getProperty("playwright.driver.tmpdir");
String prefix = "playwright-java-";
driverTempDir = alternativeTmpdir == null
? Files.createTempDirectory(prefix)
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
DriverJar() throws IOException, URISyntaxException, InterruptedException {
driverTempDir = Files.createTempDirectory("playwright-java-");
driverTempDir.toFile().deleteOnExit();
}
@Override
protected void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
extractDriverToTempDir();
if (installBrowsers)
installBrowsers(env);
installBrowsers();
}
private void installBrowsers(Map<String, String> env) throws IOException, InterruptedException {
String skip = env.get(PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD);
if (skip == null) {
skip = System.getenv(PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD);
}
if (skip != null && !"0".equals(skip) && !"false".equals(skip)) {
System.out.println("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set");
return;
}
Path driver = driverPath();
private void installBrowsers() throws IOException, InterruptedException {
String cliFileName = super.cliFileName();
Path driver = driverTempDir.resolve(cliFileName);
if (!Files.exists(driver)) {
throw new RuntimeException("Failed to find driver: " + driver);
throw new RuntimeException("Failed to find " + cliFileName + " at " + driver);
}
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "install");
pb.environment().putAll(env);
setRequiredEnvironmentVariables(pb);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
@@ -70,9 +48,6 @@ public class DriverJar extends Driver {
p.destroy();
throw new RuntimeException("Timed out waiting for browsers to install");
}
if (p.exitValue() != 0) {
throw new RuntimeException("Failed to install browsers, exit code: " + p.exitValue());
}
}
private static boolean isExecutable(Path filePath) {
@@ -82,19 +57,12 @@ public class DriverJar extends Driver {
private void extractDriverToTempDir() throws URISyntaxException, IOException {
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
URI originalUri = classloader.getResource("driver/" + platformDir()).toURI();
URI uri = maybeExtractNestedJar(originalUri);
URI uri = classloader.getResource("driver/" + platformDir()).toURI();
// Create zip filesystem if loading from jar.
try (FileSystem fileSystem = "jar".equals(uri.getScheme()) ? FileSystems.newFileSystem(uri, Collections.emptyMap()) : null) {
Path srcRoot = Paths.get(uri);
// jar file system's .relativize gives wrong results when used with
// spring-boot-maven-plugin, convert to the default filesystem to
// have predictable results.
// See https://github.com/microsoft/playwright-java/issues/306
Path srcRootDefaultFs = Paths.get(srcRoot.toString());
Files.walk(srcRoot).forEach(fromPath -> {
Path relative = srcRootDefaultFs.relativize(Paths.get(fromPath.toString()));
Path relative = srcRoot.relativize(fromPath);
Path toPath = driverTempDir.resolve(relative.toString());
try {
if (Files.isDirectory(fromPath)) {
@@ -107,38 +75,16 @@ public class DriverJar extends Driver {
}
toPath.toFile().deleteOnExit();
} catch (IOException e) {
throw new RuntimeException("Failed to extract driver from " + uri + ", full uri: " + originalUri, e);
throw new RuntimeException("Failed to extract driver from " + uri, e);
}
});
}
}
private URI maybeExtractNestedJar(final URI uri) throws URISyntaxException {
if (!"jar".equals(uri.getScheme())) {
return uri;
}
final String JAR_URL_SEPARATOR = "!/";
String[] parts = uri.toString().split("!/");
if (parts.length != 3) {
return uri;
}
String innerJar = String.join(JAR_URL_SEPARATOR, parts[0], parts[1]);
URI jarUri = new URI(innerJar);
try (FileSystem fs = FileSystems.newFileSystem(jarUri, Collections.emptyMap())) {
Path fromPath = Paths.get(jarUri);
Path toPath = driverTempDir.resolve(fromPath.getFileName().toString());
Files.copy(fromPath, toPath);
toPath.toFile().deleteOnExit();
return new URI("jar:" + toPath.toUri() + JAR_URL_SEPARATOR + parts[2]);
} catch (IOException e) {
throw new RuntimeException("Failed to extract driver's nested .jar from " + jarUri + "; full uri: " + uri, e);
}
}
private static String platformDir() {
String name = System.getProperty("os.name").toLowerCase();
if (name.contains("windows")) {
return "win32_x64";
return System.getProperty("os.arch").equals("amd64") ? "win32_x64" : "win32";
}
if (name.contains("linux")) {
return "linux";
@@ -17,44 +17,33 @@
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.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestInstall {
@BeforeEach
void clearSystemProperties() {
// Clear system property to ensure that the driver is loaded from jar.
System.clearProperty("playwright.cli.dir");
System.clearProperty("playwright.driver.tmpdir");
}
@Test
void playwrightCliInstalled() throws Exception {
Path cli = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
assertTrue(Files.exists(cli));
// Clear system property to ensure that the driver is loaded from jar.
System.clearProperty("playwright.cli.dir");
try {
Path cli = Driver.ensureDriverInstalled();
assertTrue(Files.exists(cli));
ProcessBuilder pb = new ProcessBuilder(cli.toString(), "install");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
boolean result = p.waitFor(1, TimeUnit.MINUTES);
assertTrue(result, "Timed out waiting for browsers to install");
}
@Test
void playwrightDriverInAlternativeTmpdir(@TempDir Path tmpdir) throws Exception {
System.setProperty("playwright.driver.tmpdir", tmpdir.toString());
DriverJar driver = new DriverJar();
assertTrue(driver.driverPath().startsWith(tmpdir), "Driver path: " + driver.driverPath() + " tmp: " + tmpdir);
ProcessBuilder pb = new ProcessBuilder(cli.toString(), "install");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
boolean result = p.waitFor(1, TimeUnit.MINUTES);
assertTrue(result, "Timed out waiting for browsers to install");
} catch (Exception e) {
e.printStackTrace();
assertNull(e);
}
}
}
+14 -2
View File
@@ -6,13 +6,13 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.19.0</version>
<version>0.181.0</version>
</parent>
<artifactId>driver</artifactId>
<name>Playwright - Driver</name>
<description>
This module provides API for discovery and launching of Playwright driver.
This module provides API for discovery and launching of playwright-cli binary.
</description>
<build>
@@ -24,6 +24,18 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<source>8</source>
<failOnError>false</failOnError>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -18,7 +18,6 @@ package com.microsoft.playwright.impl;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
/**
* This class provides access to playwright-cli. It can be either preinstalled
@@ -33,57 +32,27 @@ public abstract class Driver {
PreinstalledDriver(Path driverDir) {
this.driverDir = driverDir;
}
@Override
protected void initialize(Map<String, String> env, Boolean installBrowsers) {
// no-op
}
@Override
Path driverDir() {
return driverDir;
}
}
public static synchronized Path ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
public static synchronized Path ensureDriverInstalled() {
if (instance == null) {
try {
instance = createDriver();
instance.initialize(env, installBrowsers);
} catch (Exception exception) {
throw new RuntimeException("Failed to create driver", exception);
}
}
return instance.driverPath();
String name = instance.cliFileName();
return instance.driverDir().resolve(name);
}
protected abstract void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception;
public Path driverPath() {
String cliFileName = System.getProperty("os.name").toLowerCase().contains("windows") ?
protected String cliFileName() {
return System.getProperty("os.name").toLowerCase().contains("windows") ?
"playwright.cmd" : "playwright.sh";
return driverDir().resolve(cliFileName);
}
public static void setRequiredEnvironmentVariables(ProcessBuilder pb) {
pb.environment().put("PW_LANG_NAME", "java");
pb.environment().put("PW_LANG_NAME_VERSION", getMajorJavaVersion());
String version = Driver.class.getPackage().getImplementationVersion();
if (version != null) {
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
}
}
private static String getMajorJavaVersion() {
String version = System.getProperty("java.version");
if (version.startsWith("1.")) {
return version.substring(2, 3);
}
int dot = version.indexOf(".");
if (dot != -1) {
return version.substring(0, dot);
}
return version;
}
private static Driver createDriver() throws Exception {
+2 -10
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.19.0</version>
<version>0.1-SNAPSHOT</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -15,7 +15,7 @@
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.17.0</version>
<version>0.180.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
@@ -31,12 +31,4 @@
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>snapshots-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases><enabled>false</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
</project>
@@ -19,20 +19,22 @@ package org.example;
import com.microsoft.playwright.*;
public class EvaluateInBrowserContext {
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://www.example.com/");
Object dimensions = page.evaluate("() => {\n" +
" return {\n" +
" width: document.documentElement.clientWidth,\n" +
" height: document.documentElement.clientHeight,\n" +
" deviceScaleFactor: window.devicePixelRatio\n" +
" }\n" +
"}");
System.out.println(dimensions);
}
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.firefox();
Browser browser = browserType.launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://www.example.com/");
Object dimensions = page.evaluate("() => {\n" +
" return {\n" +
" width: document.documentElement.clientWidth,\n" +
" height: document.documentElement.clientHeight,\n" +
" deviceScaleFactor: window.devicePixelRatio\n" +
" }\n" +
"}");
System.out.println(dimensions);
browser.close();
playwright.close();
}
}
@@ -19,16 +19,18 @@ package org.example;
import com.microsoft.playwright.*;
public class InterceptNetworkRequests {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.webkit().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.route("**", route -> {
System.out.println(route.request().url());
route.resume();
});
page.navigate("http://todomvc.com");
}
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.webkit();
Browser browser = browserType.launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.route("**", route -> {
System.out.println(route.request().url());
route.continue_();
});
page.navigate("http://todomvc.com");
browser.close();
playwright.close();
}
}
@@ -16,7 +16,6 @@
package org.example;
import com.microsoft.playwright.options.*;
import com.microsoft.playwright.*;
import java.nio.file.Paths;
@@ -24,22 +23,21 @@ import java.nio.file.Paths;
import static java.util.Arrays.asList;
public class MobileAndGeolocation {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36")
.setViewportSize(411, 731)
.setDeviceScaleFactor(2.625)
.setIsMobile(true)
.setHasTouch(true)
.setLocale("en-US")
.setGeolocation(41.889938, 12.492507)
.setPermissions(asList("geolocation")));
Page page = context.newPage();
page.navigate("https://www.openstreetmap.org/");
page.click("a[data-original-title=\"Show My Location\"]");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("colosseum-pixel2.png")));
}
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.chromium();
Browser browser = browserType.launch(new BrowserType.LaunchOptions().withHeadless(false));
DeviceDescriptor pixel2 = playwright.devices().get("Pixel 2");
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.withDevice(pixel2)
.withLocale("en-US")
.withGeolocation(new Geolocation(41.889938, 12.492507))
.withPermissions(asList("geolocation")));
Page page = context.newPage();
page.navigate("https://www.openstreetmap.org/");
page.click("a[data-original-title=\"Show My Location\"]");
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("colosseum-pixel2.png")));
browser.close();
playwright.close();
}
}
@@ -23,21 +23,22 @@ import java.util.Arrays;
import java.util.List;
public class PageScreenshot {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
List<BrowserType> browserTypes = Arrays.asList(
playwright.chromium(),
playwright.webkit(),
playwright.firefox()
);
for (BrowserType browserType : browserTypes) {
try (Browser browser = browserType.launch()) {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("screenshot-" + browserType.name() + ".png")));
}
}
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
List<BrowserType> browserTypes = Arrays.asList(
playwright.chromium(),
playwright.webkit(),
playwright.firefox()
);
for (BrowserType browserType : browserTypes) {
Browser browser = browserType.launch();
BrowserContext context = browser.newContext(
new Browser.NewContextOptions().withViewport(800, 600));
Page page = context.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("screenshot-" + browserType.name() + ".png")));
browser.close();
}
playwright.close();
}
}
@@ -1,30 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.example;
import com.microsoft.playwright.*;
public class PrintTitle {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
Page page = browser.newPage();
page.navigate("http://playwright.dev");
System.out.println(page.title());
}
}
}
@@ -1,31 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.example;
import com.microsoft.playwright.*;
import java.nio.file.Paths;
public class WebKitScreenshot {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.webkit().launch();
Page page = browser.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("example.png")));
}
}
}
+19 -26
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.19.0</version>
<version>0.181.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -31,7 +31,17 @@
<configuration>
<subpackages>com.microsoft.playwright</subpackages>
<excludePackageNames>com.microsoft.playwright.impl</excludePackageNames>
<additionalOptions>--allow-script-in-comments</additionalOptions>
<failOnError>false</failOnError>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -40,43 +50,21 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
</plugins>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<targetPath>resources</targetPath>
</testResource>
</testResources>
</build>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</dependency>
<dependency>
<groupId>org.opentest4j</groupId>
<artifactId>opentest4j</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>driver</artifactId>
@@ -85,5 +73,10 @@
<groupId>com.microsoft.playwright</groupId>
<artifactId>driver-bundle</artifactId>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -1,184 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
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>.
*/
public interface APIRequest {
class NewContextOptions {
/**
* Methods like {@link APIRequestContext#get APIRequestContext.get()} take the base URL into consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and sending request to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and sending request to {@code ./bar.html} results in
* {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public String baseURL;
/**
* An object containing additional HTTP headers to be sent with every request.
*/
public Map<String, String> extraHTTPHeaders;
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public HttpCredentials httpCredentials;
/**
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public Boolean ignoreHTTPSErrors;
/**
* Network proxy settings.
*/
public Proxy proxy;
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()} or {@link APIRequestContext#storageState
* APIRequestContext.storageState()}. Either a path to the file with saved storage, or the value returned by one of {@link
* BrowserContext#storageState BrowserContext.storageState()} or {@link APIRequestContext#storageState
* APIRequestContext.storageState()} methods.
*/
public String storageState;
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}. Path to the file with saved storage
* state.
*/
public Path storageStatePath;
/**
* Maximum time in milliseconds to wait for the response. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
*/
public Double timeout;
/**
* Specific user agent to use in this context.
*/
public String userAgent;
/**
* Methods like {@link APIRequestContext#get APIRequestContext.get()} take the base URL into consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and sending request to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and sending request to {@code ./bar.html} results in
* {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public NewContextOptions setBaseURL(String baseURL) {
this.baseURL = baseURL;
return this;
}
/**
* An object containing additional HTTP headers to be sent with every request.
*/
public NewContextOptions setExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public NewContextOptions setHttpCredentials(String username, String password) {
return setHttpCredentials(new HttpCredentials(username, password));
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
*/
public NewContextOptions setHttpCredentials(HttpCredentials httpCredentials) {
this.httpCredentials = httpCredentials;
return this;
}
/**
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public NewContextOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
/**
* Network proxy settings.
*/
public NewContextOptions setProxy(String server) {
return setProxy(new Proxy(server));
}
/**
* Network proxy settings.
*/
public NewContextOptions setProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()} or {@link APIRequestContext#storageState
* APIRequestContext.storageState()}. Either a path to the file with saved storage, or the value returned by one of {@link
* BrowserContext#storageState BrowserContext.storageState()} or {@link APIRequestContext#storageState
* APIRequestContext.storageState()} methods.
*/
public NewContextOptions setStorageState(String storageState) {
this.storageState = storageState;
return this;
}
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}. Path to the file with saved storage
* state.
*/
public NewContextOptions setStorageStatePath(Path storageStatePath) {
this.storageStatePath = storageStatePath;
return this;
}
/**
* Maximum time in milliseconds to wait for the response. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
*/
public NewContextOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* Specific user agent to use in this context.
*/
public NewContextOptions setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
}
/**
* Creates new instances of {@code APIRequestContext}.
*/
default APIRequestContext newContext() {
return newContext(null);
}
/**
* Creates new instances of {@code APIRequestContext}.
*/
APIRequestContext newContext(NewContextOptions options);
}
@@ -1,212 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
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.
*/
public interface APIRequestContext {
class StorageStateOptions {
/**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
*/
public Path path;
/**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
*/
public StorageStateOptions setPath(Path path) {
this.path = path;
return this;
}
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE">DELETE</a> request and returns
* its response. The method will populate request cookies from the context and update context cookies from the response.
* The method will automatically follow redirects.
*
* @param url Target URL.
*/
default APIResponse delete(String url) {
return delete(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE">DELETE</a> request and returns
* its response. The method will populate request cookies from the context and update context cookies from the response.
* The method will automatically follow redirects.
*
* @param url Target URL.
* @param params Optional request parameters.
*/
APIResponse delete(String url, RequestOptions params);
/**
* All responses returned by {@link APIRequestContext#get APIRequestContext.get()} and similar methods are stored in the
* memory, so that you can later call {@link APIResponse#body APIResponse.body()}. This method discards all stored
* responses, and makes {@link APIResponse#body APIResponse.body()} throw "Response disposed" error.
*/
void dispose();
/**
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
* context cookies from the response. The method will automatically follow redirects.
*
* @param urlOrRequest Target URL or Request to get all parameters from.
*/
default APIResponse fetch(String urlOrRequest) {
return fetch(urlOrRequest, null);
}
/**
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
* context cookies from the response. The method will automatically follow redirects.
*
* @param urlOrRequest Target URL or Request to get all parameters from.
* @param params Optional request parameters.
*/
APIResponse fetch(String urlOrRequest, RequestOptions params);
/**
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
* context cookies from the response. The method will automatically follow redirects.
*
* @param urlOrRequest Target URL or Request to get all parameters from.
*/
default APIResponse fetch(Request urlOrRequest) {
return fetch(urlOrRequest, null);
}
/**
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
* context cookies from the response. The method will automatically follow redirects.
*
* @param urlOrRequest Target URL or Request to get all parameters from.
* @param params Optional request parameters.
*/
APIResponse fetch(Request urlOrRequest, RequestOptions params);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET">GET</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* @param url Target URL.
*/
default APIResponse get(String url) {
return get(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET">GET</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* @param url Target URL.
* @param params Optional request parameters.
*/
APIResponse get(String url, RequestOptions params);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD">HEAD</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* @param url Target URL.
*/
default APIResponse head(String url) {
return head(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD">HEAD</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* @param url Target URL.
* @param params Optional request parameters.
*/
APIResponse head(String url, RequestOptions params);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH">PATCH</a> request and returns
* its response. The method will populate request cookies from the context and update context cookies from the response.
* The method will automatically follow redirects.
*
* @param url Target URL.
*/
default APIResponse patch(String url) {
return patch(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH">PATCH</a> request and returns
* its response. The method will populate request cookies from the context and update context cookies from the response.
* The method will automatically follow redirects.
*
* @param url Target URL.
* @param params Optional request parameters.
*/
APIResponse patch(String url, RequestOptions params);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">POST</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* @param url Target URL.
*/
default APIResponse post(String url) {
return post(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">POST</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* @param url Target URL.
* @param params Optional request parameters.
*/
APIResponse post(String url, RequestOptions params);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT">PUT</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* @param url Target URL.
*/
default APIResponse put(String url) {
return put(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT">PUT</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* @param url Target URL.
* @param params Optional request parameters.
*/
APIResponse put(String url, RequestOptions params);
/**
* Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to
* the constructor.
*/
default String storageState() {
return storageState(null);
}
/**
* Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to
* the constructor.
*/
String storageState(StorageStateOptions options);
}
@@ -1,65 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* {@code APIResponse} class represents responses returned by {@link APIRequestContext#get APIRequestContext.get()} and similar
* methods.
*/
public interface APIResponse {
/**
* Returns the buffer with response body.
*/
byte[] body();
/**
* Disposes the body of this response. If not called then the body will stay in memory until the context closes.
*/
void dispose();
/**
* An object with all the response HTTP headers associated with this response.
*/
Map<String, String> headers();
/**
* An array with all the request HTTP headers associated with this response. Header names are not lower-cased. Headers with
* multiple entries, such as {@code Set-Cookie}, appear in the array multiple times.
*/
List<HttpHeader> headersArray();
/**
* Contains a boolean stating whether the response was successful (status in the range 200-299) or not.
*/
boolean ok();
/**
* Contains the status code of the response (e.g., 200 for a success).
*/
int status();
/**
* Contains the status text of the response (e.g. usually an "OK" for a success).
*/
String statusText();
/**
* Returns the text representation of response body.
*/
String text();
/**
* Contains the URL of the response.
*/
String url();
}
@@ -0,0 +1,68 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import java.util.*;
/**
* The Accessibility class provides methods for inspecting Chromium's accessibility tree. The accessibility tree is used by
* assistive technology such as [screen readers](https://en.wikipedia.org/wiki/Screen_reader) or
* [switches](https://en.wikipedia.org/wiki/Switch_access).
*
* <p> Accessibility is a very platform-specific thing. On different platforms, there are different screen readers that might
* have wildly different output.
*
* <p> Rendering engines of Chromium, Firefox and Webkit have a concept of "accessibility tree", which is then translated into
* different platform-specific APIs. Accessibility namespace gives access to this Accessibility Tree.
*
* <p> Most of the accessibility tree gets filtered out when converting from internal browser AX Tree to Platform-specific
* AX-Tree or by assistive technologies themselves. By default, Playwright tries to approximate this filtering, exposing
* only the "interesting" nodes of the tree.
*/
public interface Accessibility {
class SnapshotOptions {
/**
* Prune uninteresting nodes from the tree. Defaults to {@code true}.
*/
public Boolean interestingOnly;
/**
* The root DOM element for the snapshot. Defaults to the whole page.
*/
public ElementHandle root;
public SnapshotOptions withInterestingOnly(boolean interestingOnly) {
this.interestingOnly = interestingOnly;
return this;
}
public SnapshotOptions withRoot(ElementHandle root) {
this.root = root;
return this;
}
}
default AccessibilityNode snapshot() {
return snapshot(null);
}
/**
* Captures the current state of the accessibility tree. The returned object represents the root accessible node of the
* page.
*
* <p> <strong>NOTE:</strong> The Chromium accessibility tree contains nodes that go unused on most platforms and by most screen readers.
* Playwright will discard them as well for an easier to process tree, unless {@code interestingOnly} is set to {@code false}.
*/
AccessibilityNode snapshot(SnapshotOptions options);
}
@@ -0,0 +1,51 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import java.util.List;
public interface AccessibilityNode {
String role();
String name();
String valueString();
Double valueNumber();
String description();
String keyshortcuts();
String roledescription();
String valuetext();
Boolean disabled();
Boolean expanded();
Boolean focused();
Boolean modal();
Boolean multiline();
Boolean multiselectable();
Boolean readonly();
Boolean required();
Boolean selected();
enum CheckedState { CHECKED, UNCHECKED, MIXED }
CheckedState checked();
enum PressedState { PRESSED, RELEASED, MIXED }
PressedState pressed();
Integer level();
Double valuemin();
Double valuemax();
String autocomplete();
String haspopup();
String invalid();
String orientation();
List<AccessibilityNode> children();
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,44 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.impl.Driver;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import static java.util.Arrays.asList;
/**
* Use this class to launch playwright cli.
*/
public class CLI {
public static void main(String[] args) throws IOException, InterruptedException {
Path driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
ProcessBuilder pb = new ProcessBuilder(driver.toString());
pb.command().addAll(asList(args));
Driver.setRequiredEnvironmentVariables(pb);
String version = Playwright.class.getPackage().getImplementationVersion();
if (version != null) {
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
}
pb.inheritIO();
Process process = pb.start();
System.exit(process.waitFor());
}
}
@@ -0,0 +1,3 @@
package com.microsoft.playwright;
public enum ColorScheme { DARK, LIGHT, NO_PREFERENCE }
@@ -19,25 +19,40 @@ 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 [{@code event: Page.console}] event.
*/
public interface ConsoleMessage {
/**
* List of arguments passed to a {@code console} function call. See also {@link Page#onConsoleMessage Page.onConsoleMessage()}.
*/
class Location {
/**
* URL of the resource.
*/
private String url;
/**
* 0-based line number in the resource.
*/
private int lineNumber;
/**
* 0-based column number in the resource.
*/
private int columnNumber;
public String url() {
return this.url;
}
public int lineNumber() {
return this.lineNumber;
}
public int columnNumber() {
return this.columnNumber;
}
}
List<JSHandle> args();
/**
* URL of the resource followed by 0-based line and column numbers in the resource formatted as {@code URL:line:column}.
*/
String location();
/**
* The text of the console message.
*/
Location location();
String text();
/**
* One of the following values: {@code "log"}, {@code "debug"}, {@code "info"}, {@code "error"}, {@code "warning"}, {@code "dir"}, {@code "dirxml"}, {@code "table"},
* {@code "trace"}, {@code "clear"}, {@code "startGroup"}, {@code "startGroupCollapsed"}, {@code "endGroup"}, {@code "assert"}, {@code "profile"}, {@code "profileEnd"},
* {@code "count"}, {@code "timeEnd"}.
* One of the following values: {@code 'log'}, {@code 'debug'}, {@code 'info'}, {@code 'error'}, {@code 'warning'}, {@code 'dir'}, {@code 'dirxml'}, {@code 'table'},
* {@code 'trace'}, {@code 'clear'}, {@code 'startGroup'}, {@code 'startGroupCollapsed'}, {@code 'endGroup'}, {@code 'assert'}, {@code 'profile'}, {@code 'profileEnd'},
* {@code 'count'}, {@code 'timeEnd'}.
*/
String type();
}
@@ -1,12 +1,12 @@
/*
* Copyright (c) Microsoft Corporation.
*
* <p>
* 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
*
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
*
* <p>
* 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.
@@ -14,15 +14,17 @@
* limitations under the License.
*/
package com.microsoft.playwright.impl;
package com.microsoft.playwright;
import com.microsoft.playwright.PlaywrightException;
import java.io.PrintStream;
import java.io.PrintWriter;
class DriverException extends PlaywrightException {
DriverException(SerializedError.Error error) {
super(error.toString());
public interface DeviceDescriptor {
interface Viewport {
int width();
int height();
}
Viewport viewport();
String userAgent();
double deviceScaleFactor();
boolean isMobile();
boolean hasTouch();
BrowserType defaultBrowserType();
}
@@ -16,41 +16,14 @@
package com.microsoft.playwright;
import java.util.*;
/**
* {@code Dialog} objects are dispatched by page via the {@link Page#onDialog Page.onDialog()} event.
*
* <p> An example of using {@code Dialog} class:
* <pre>{@code
* import com.microsoft.playwright.*;
*
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType chromium = playwright.chromium();
* Browser browser = chromium.launch();
* Page page = browser.newPage();
* page.onDialog(dialog -> {
* System.out.println(dialog.message());
* dialog.dismiss();
* });
* page.evaluate("alert('1')");
* browser.close();
* }
* }
* }
* }</pre>
*
* <p> <strong>NOTE:</strong> Dialogs are dismissed automatically, unless there is a {@link Page#onDialog Page.onDialog()} listener. When listener is
* present, it **must** either {@link Dialog#accept Dialog.accept()} or {@link Dialog#dismiss Dialog.dismiss()} the dialog
* - otherwise the page will <a
* href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking">freeze</a> waiting for the
* dialog, and actions like click will never finish.
* {@code Dialog} objects are dispatched by page via the [{@code event: Page.dialog}] event.
*/
public interface Dialog {
/**
* Returns when the dialog has been accepted.
*/
enum Type { ALERT, BEFOREUNLOAD, CONFIRM, PROMPT }
default void accept() {
accept(null);
}
@@ -75,6 +48,6 @@ public interface Dialog {
/**
* Returns dialog's type, can be one of {@code alert}, {@code beforeunload}, {@code confirm} or {@code prompt}.
*/
String type();
Type type();
}
@@ -18,71 +18,48 @@ package com.microsoft.playwright;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.*;
/**
* {@code Download} objects are dispatched by page via the {@link Page#onDownload Page.onDownload()} event.
* {@code Download} objects are dispatched by page via the [{@code event: Page.download}] event.
*
* <p> All the downloaded files belonging to the browser context are deleted when the browser context is closed.
* <p> All the downloaded files belonging to the browser context are deleted when the browser context is closed. All downloaded
* files are deleted when the browser closes.
*
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
* <pre>{@code
* // wait for download to start
* Download download = page.waitForDownload(() -> page.click("a"));
* // wait for download to complete
* Path path = download.path();
* }</pre>
* <pre>{@code
* // wait for download to start
* Download download = page.waitForDownload(() -> {
* page.click("a");
* });
* // wait for download to complete
* Path path = download.path();
* }</pre>
*
* <p> <strong>NOTE:</strong> Browser context **must** be created with the {@code acceptDownloads} set to {@code true} when user needs access to the
* downloaded content. If {@code acceptDownloads} is not set, download events are emitted, but the actual download is not
* performed and user has no access to the downloaded files.
*/
public interface Download {
/**
* Cancels a download. Will not fail if the download is already finished or canceled. Upon successful cancellations,
* {@code download.failure()} would resolve to {@code "canceled"}.
*/
void cancel();
/**
* Returns readable stream for current download or {@code null} if download failed.
*/
InputStream createReadStream();
/**
* Deletes the downloaded file. Will wait for the download to finish if necessary.
* Deletes the downloaded file.
*/
void delete();
/**
* Returns download error if any. Will wait for the download to finish if necessary.
* Returns download error if any.
*/
String failure();
/**
* Get the page that the download belongs to.
*/
Page page();
/**
* Returns path to the downloaded file in case of successful download. The method will wait for the download to finish if
* necessary. The method throws when connected remotely.
*
* <p> Note that the download's file name is a random GUID, use {@link Download#suggestedFilename Download.suggestedFilename()}
* to get suggested file name.
* Returns path to the downloaded file in case of successful download.
*/
Path path();
/**
* Copy the download to a user-specified path. It is safe to call this method while the download is still in progress. Will
* wait for the download to finish if necessary.
* Saves the download to a user-specified path.
*
* @param path Path where the download should be copied.
* @param path Path where the download should be saved.
*/
void saveAs(Path path);
/**
* Returns suggested filename for this download. It is typically computed by the browser from the <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition">{@code Content-Disposition}</a> response
* header or the {@code download} attribute. See the spec on <a
* href="https://html.spec.whatwg.org/#downloading-resources">whatwg</a>. Different browsers can use different logic for
* computing it.
* Returns suggested filename for this download. It is typically computed by the browser from the
* [{@code Content-Disposition}](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) response header
* or the {@code download} attribute. See the spec on [whatwg](https://html.spec.whatwg.org/#downloading-resources). Different
* browsers can use different logic for computing it.
*/
String suggestedFilename();
/**
File diff suppressed because it is too large Load Diff
@@ -14,8 +14,9 @@
* limitations under the License.
*/
package com.microsoft.playwright.options;
package com.microsoft.playwright;
public interface FunctionCallback {
Object call(Object... args);
public interface Event<EventType> {
EventType type();
Object data();
}
@@ -16,17 +16,25 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
import java.util.*;
/**
* {@code FileChooser} objects are dispatched by the page in the {@link Page#onFileChooser Page.onFileChooser()} event.
* <pre>{@code
* FileChooser fileChooser = page.waitForFileChooser(() -> page.click("upload"));
* fileChooser.setFiles(Paths.get("myfile.pdf"));
* }</pre>
* {@code FileChooser} objects are dispatched by the page in the [{@code event: Page.filechooser}] event.
*/
public interface FileChooser {
class FilePayload {
public final String name;
public final String mimeType;
public final byte[] buffer;
public FilePayload(String name, String mimeType, byte[] buffer) {
this.name = name;
this.mimeType = mimeType;
this.buffer = buffer;
}
}
class SetFilesOptions {
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
@@ -36,26 +44,15 @@ public interface FileChooser {
public Boolean noWaitAfter;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
* using the [{@code method: BrowserContext.setDefaultTimeout}] or [{@code method: Page.setDefaultTimeout}] methods.
*/
public Double timeout;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public SetFilesOptions setNoWaitAfter(boolean noWaitAfter) {
public SetFilesOptions withNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
*/
public SetFilesOptions setTimeout(double timeout) {
public SetFilesOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
@@ -72,53 +69,17 @@ public interface FileChooser {
* Returns page this file chooser belongs to.
*/
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.
*/
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.
*/
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.
*/
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.
*/
default void setFiles(Path file) { setFiles(file, null); }
default void setFiles(Path file, SetFilesOptions options) { setFiles(new Path[]{ file }, options); }
default void setFiles(Path[] files) { setFiles(files, null); }
void setFiles(Path[] files, SetFilesOptions options);
default void setFiles(FileChooser.FilePayload file) { setFiles(file, null); }
default void setFiles(FileChooser.FilePayload file, SetFilesOptions options) { setFiles(new FileChooser.FilePayload[]{ file }, options); }
default void setFiles(FileChooser.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.
*/
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.
*/
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.
*/
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.
*/
void setFiles(FilePayload[] files, SetFilesOptions options);
void setFiles(FileChooser.FilePayload[] files, SetFilesOptions options);
}
File diff suppressed because it is too large Load Diff
@@ -1,129 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import java.util.regex.Pattern;
/**
* FrameLocator represents a view to the {@code iframe} on the page. It captures the logic sufficient to retrieve the {@code iframe}
* and locate elements in that iframe. FrameLocator can be created with either {@link Page#frameLocator
* Page.frameLocator()} or {@link Locator#frameLocator Locator.frameLocator()} method.
* <pre>{@code
* Locator locator = page.frameLocator("#my-frame").locator("text=Submit");
* locator.click();
* }</pre>
*
* <p> **Strictness**
*
* <p> Frame locators are strict. This means that all operations on frame locators will throw if more than one element matches
* given selector.
* <pre>{@code
* // Throws if there are several frames in DOM:
* page.frame_locator(".result-frame").locator("button").click();
*
* // Works because we explicitly tell locator to pick the first frame:
* page.frame_locator(".result-frame").first().locator("button").click();
* }</pre>
*
* <p> **Converting Locator to FrameLocator**
*
* <p> If you have a {@code Locator} object pointing to an {@code iframe} it can be converted to {@code FrameLocator} using <a
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/:scope">{@code :scope}</a> CSS selector:
* <pre>{@code
* Locator frameLocator = locator.frameLocator(':scope');
* }</pre>
*/
public interface FrameLocator {
class LocatorOptions {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
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>}.
*/
public Object hasText;
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public LocatorOptions setHas(Locator has) {
this.has = has;
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>}.
*/
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>}.
*/
public LocatorOptions setHasText(Pattern hasText) {
this.hasText = hasText;
return this;
}
}
/**
* Returns locator to the first matching frame.
*/
FrameLocator first();
/**
* 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
* selectors</a> for more details.
*/
FrameLocator frameLocator(String selector);
/**
* Returns locator to the last matching frame.
*/
FrameLocator last();
/**
* 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
* selectors</a> for more details.
*/
default Locator locator(String selector) {
return locator(selector, null);
}
/**
* 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
* selectors</a> for more details.
*/
Locator locator(String selector, LocatorOptions options);
/**
* Returns locator to the n-th matching frame.
*/
FrameLocator nth(int index);
}
@@ -14,31 +14,36 @@
* limitations under the License.
*/
package com.microsoft.playwright.options;
package com.microsoft.playwright;
public class Geolocation {
/**
* Latitude between -90 and 90.
*/
public double latitude;
/**
* Longitude between -180 and 180.
*/
public double longitude;
/**
* Non-negative accuracy value. Defaults to {@code 0}.
*/
public Double accuracy;
public Geolocation() {
}
public Geolocation(double latitude, double longitude) {
this(latitude, longitude, null);
}
public Geolocation(double latitude, double longitude, Double accuracy) {
this.latitude = latitude;
this.longitude = longitude;
this.accuracy = accuracy;
}
/**
* Non-negative accuracy value. Defaults to {@code 0}.
*/
public Geolocation setAccuracy(double accuracy) {
public Geolocation withLatitude(double latitude) {
this.latitude = latitude;
return this;
}
public Geolocation withLongitude(double longitude) {
this.longitude = longitude;
return this;
}
public Geolocation withAccuracy(double accuracy) {
this.accuracy = accuracy;
return this;
}
}
}
@@ -19,19 +19,15 @@ package com.microsoft.playwright;
import java.util.*;
/**
* JSHandle represents an in-page JavaScript object. JSHandles can be created with the {@link Page#evaluateHandle
* Page.evaluateHandle()} method.
* <pre>{@code
* JSHandle windowHandle = page.evaluateHandle("() => window");
* // ...
* }</pre>
* JSHandle represents an in-page JavaScript object. JSHandles can be created with the [{@code method: Page.evaluateHandle}]
* method.
*
* <p> JSHandle prevents the referenced JavaScript object being garbage collected unless the handle is exposed with {@link
* JSHandle#dispose JSHandle.dispose()}. JSHandles are auto-disposed when their origin frame gets navigated or the parent
* context gets destroyed.
* <p> JSHandle prevents the referenced JavaScript object being garbage collected unless the handle is exposed with
* [{@code method: JSHandle.dispose}]. JSHandles are auto-disposed when their origin frame gets navigated or the parent context
* gets destroyed.
*
* <p> JSHandle instances can be used as an argument in {@link Page#evalOnSelector Page.evalOnSelector()}, {@link Page#evaluate
* Page.evaluate()} and {@link Page#evaluateHandle Page.evaluateHandle()} methods.
* <p> JSHandle instances can be used as an argument in [{@code method: Page.$eval}], [{@code method: Page.evaluate}] and
* [{@code method: Page.evaluateHandle}] methods.
*/
public interface JSHandle {
/**
@@ -42,95 +38,44 @@ public interface JSHandle {
* The {@code jsHandle.dispose} method stops referencing the element handle.
*/
void dispose();
/**
* Returns the return value of {@code expression}.
*
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> If {@code expression} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@code handle.evaluate} would wait for the promise to resolve and return its value.
*
* <p> Examples:
* <pre>{@code
* ElementHandle tweetHandle = page.querySelector(".tweet .retweets");
* assertEquals("10 retweets", tweetHandle.evaluate("node => node.innerText"));
* }</pre>
*
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
*/
default Object evaluate(String expression) {
return evaluate(expression, null);
default Object evaluate(String pageFunction) {
return evaluate(pageFunction, null);
}
/**
* Returns the return value of {@code expression}.
* Returns the return value of {@code pageFunction}
*
* <p> This method passes this handle as the first argument to {@code expression}.
* <p> This method passes this handle as the first argument to {@code pageFunction}.
*
* <p> If {@code expression} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@code handle.evaluate} would wait for the promise to resolve and return its value.
* <p> If {@code pageFunction} returns a [Promise], then {@code handle.evaluate} would wait for the promise to resolve and return its
* value.
*
* <p> Examples:
* <pre>{@code
* ElementHandle tweetHandle = page.querySelector(".tweet .retweets");
* assertEquals("10 retweets", tweetHandle.evaluate("node => node.innerText"));
* }</pre>
*
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
* @param arg Optional argument to pass to {@code expression}.
* @param pageFunction Function to be evaluated in browser context
* @param arg Optional argument to pass to {@code pageFunction}
*/
Object evaluate(String expression, Object arg);
/**
* Returns the return value of {@code expression} as a {@code JSHandle}.
*
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code jsHandle.evaluateHandle} returns
* {@code JSHandle}.
*
* <p> If the function passed to the {@code jsHandle.evaluateHandle} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@code jsHandle.evaluateHandle} would wait for the promise to resolve and return its value.
*
* <p> See {@link Page#evaluateHandle Page.evaluateHandle()} for more details.
*
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
*/
default JSHandle evaluateHandle(String expression) {
return evaluateHandle(expression, null);
Object evaluate(String pageFunction, Object arg);
default JSHandle evaluateHandle(String pageFunction) {
return evaluateHandle(pageFunction, null);
}
/**
* Returns the return value of {@code expression} as a {@code JSHandle}.
* Returns the return value of {@code pageFunction} as in-page object (JSHandle).
*
* <p> This method passes this handle as the first argument to {@code expression}.
* <p> This method passes this handle as the first argument to {@code pageFunction}.
*
* <p> The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code jsHandle.evaluateHandle} returns
* {@code JSHandle}.
* in-page object (JSHandle).
*
* <p> If the function passed to the {@code jsHandle.evaluateHandle} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@code jsHandle.evaluateHandle} would wait for the promise to resolve and return its value.
* <p> If the function passed to the {@code jsHandle.evaluateHandle} returns a [Promise], then {@code jsHandle.evaluateHandle} would wait
* for the promise to resolve and return its value.
*
* <p> See {@link Page#evaluateHandle Page.evaluateHandle()} for more details.
* <p> See [{@code method: Page.evaluateHandle}] for more details.
*
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
* @param arg Optional argument to pass to {@code expression}.
* @param pageFunction Function to be evaluated
* @param arg Optional argument to pass to {@code pageFunction}
*/
JSHandle evaluateHandle(String expression, Object arg);
JSHandle evaluateHandle(String pageFunction, Object arg);
/**
* The method returns a map with **own property names** as keys and JSHandle instances for the property values.
* <pre>{@code
* JSHandle handle = page.evaluateHandle("() => ({window, document}"););
* Map<String, JSHandle> properties = handle.getProperties();
* JSHandle windowHandle = properties.get("window");
* JSHandle documentHandle = properties.get("document");
* handle.dispose();
* }</pre>
*/
Map<String, JSHandle> getProperties();
/**
@@ -142,8 +87,8 @@ public interface JSHandle {
/**
* Returns a JSON representation of the object. If the object has a {@code toJSON} function, it **will not be called**.
*
* <p> <strong>NOTE:</strong> The method will return an empty JSON object if the referenced object is not stringifiable. It will throw an error if the
* object has circular references.
* <p> <strong>NOTE:</strong> The method will return an empty JSON object if the referenced object is not stringifiable. It will throw an
* error if the object has circular references.
*/
Object jsonValue();
}
@@ -16,53 +16,27 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* Keyboard provides an api for managing a virtual keyboard. The high level api is {@link Keyboard#type Keyboard.type()},
* which takes raw characters and generates proper keydown, keypress/input, and keyup events on your page.
* Keyboard provides an api for managing a virtual keyboard. The high level api is [{@code method: Keyboard.type}], which takes
* raw characters and generates proper keydown, keypress/input, and keyup events on your page.
*
* <p> For finer control, you can use {@link Keyboard#down Keyboard.down()}, {@link Keyboard#up Keyboard.up()}, and {@link
* Keyboard#insertText Keyboard.insertText()} to manually fire events as if they were generated from a real keyboard.
*
* <p> An example of holding down {@code Shift} in order to select and delete some text:
* <pre>{@code
* page.keyboard().type("Hello World!");
* page.keyboard().press("ArrowLeft");
* page.keyboard().down("Shift");
* for (int i = 0; i < " World".length(); i++)
* page.keyboard().press("ArrowLeft");
* page.keyboard().up("Shift");
* page.keyboard().press("Backspace");
* // Result text will end up saying "Hello!"
* }</pre>
*
* <p> An example of pressing uppercase {@code A}
* <pre>{@code
* page.keyboard().press("Shift+KeyA");
* // or
* page.keyboard().press("Shift+A");
* }</pre>
* <p> For finer control, you can use [{@code method: Keyboard.down}], [{@code method: Keyboard.up}], and [{@code method: Keyboard.insertText}]
* to manually fire events as if they were generated from a real keyboard.
*
* <p> An example to trigger select-all with the keyboard
* <pre>{@code
* // on Windows and Linux
* page.keyboard().press("Control+A");
* // on macOS
* page.keyboard().press("Meta+A");
* }</pre>
*/
public interface Keyboard {
enum Modifier { ALT, CONTROL, META, SHIFT }
class PressOptions {
/**
* Time to wait between {@code keydown} and {@code keyup} in milliseconds. Defaults to 0.
*/
public Double delay;
/**
* Time to wait between {@code keydown} and {@code keyup} in milliseconds. Defaults to 0.
*/
public PressOptions setDelay(double delay) {
public PressOptions withDelay(double delay) {
this.delay = delay;
return this;
}
@@ -73,10 +47,7 @@ public interface Keyboard {
*/
public Double delay;
/**
* Time to wait between key presses in milliseconds. Defaults to 0.
*/
public TypeOptions setDelay(double delay) {
public TypeOptions withDelay(double delay) {
this.delay = delay;
return this;
}
@@ -84,10 +55,9 @@ public interface Keyboard {
/**
* Dispatches a {@code keydown} event.
*
* <p> {@code key} can specify the intended <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
* character to generate the text for. A superset of the {@code key} values can be found <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
* <p> {@code key} can specify the intended [keyboardEvent.key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key)
* value or a single character to generate the text for. A superset of the {@code key} values can be found
* [here](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values). Examples of the keys are:
*
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus}, {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab},
* {@code Delete}, {@code Escape}, {@code ArrowDown}, {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code ArrowUp}, etc.
@@ -100,11 +70,11 @@ public interface Keyboard {
* texts.
*
* <p> If {@code key} is a modifier key, {@code Shift}, {@code Meta}, {@code Control}, or {@code Alt}, subsequent key presses will be sent with that modifier
* active. To release the modifier key, use {@link Keyboard#up Keyboard.up()}.
* active. To release the modifier key, use [{@code method: Keyboard.up}].
*
* <p> After the key is pressed once, subsequent calls to {@link Keyboard#down Keyboard.down()} will have <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat">repeat</a> set to true. To release the key,
* use {@link Keyboard#up Keyboard.up()}.
* <p> After the key is pressed once, subsequent calls to [{@code method: Keyboard.down}] will have
* [repeat](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat) set to true. To release the key, use
* [{@code method: Keyboard.up}].
*
* <p> <strong>NOTE:</strong> Modifier keys DO influence {@code keyboard.down}. Holding down {@code Shift} will type the text in upper case.
*
@@ -113,57 +83,19 @@ public interface Keyboard {
void down(String key);
/**
* Dispatches only {@code input} event, does not emit the {@code keydown}, {@code keyup} or {@code keypress} events.
* <pre>{@code
* page.keyboard().insertText("嗨");
* }</pre>
*
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.insertText}. Holding down {@code Shift} will not type the text in upper case.
*
* @param text Sets input to the specified text value.
*/
void insertText(String text);
/**
* {@code key} can specify the intended <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
* character to generate the text for. A superset of the {@code key} values can be found <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
*
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus}, {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab},
* {@code Delete}, {@code Escape}, {@code ArrowDown}, {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code ArrowUp}, etc.
*
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code ShiftLeft}.
*
* <p> Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
*
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate different respective
* texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with the
* modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <pre>{@code
* Page page = browser.newPage();
* page.navigate("https://keycode.info");
* page.keyboard().press("A");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png"));
* page.keyboard().press("ArrowLeft");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
* page.keyboard().press("Shift+O");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("O.png")));
* browser.close();
* }</pre>
*
* <p> Shortcut for {@link Keyboard#down Keyboard.down()} and {@link Keyboard#up Keyboard.up()}.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
*/
default void press(String key) {
press(key, null);
}
/**
* {@code key} can specify the intended <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
* character to generate the text for. A superset of the {@code key} values can be found <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
* {@code key} can specify the intended [keyboardEvent.key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key)
* value or a single character to generate the text for. A superset of the {@code key} values can be found
* [here](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values). Examples of the keys are:
*
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus}, {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab},
* {@code Delete}, {@code Escape}, {@code ArrowDown}, {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code ArrowUp}, etc.
@@ -175,63 +107,27 @@ public interface Keyboard {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate different respective
* texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with the
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When speficied with the
* modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <pre>{@code
* Page page = browser.newPage();
* page.navigate("https://keycode.info");
* page.keyboard().press("A");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png"));
* page.keyboard().press("ArrowLeft");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
* page.keyboard().press("Shift+O");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("O.png")));
* browser.close();
* }</pre>
*
* <p> Shortcut for {@link Keyboard#down Keyboard.down()} and {@link Keyboard#up Keyboard.up()}.
* <p> Shortcut for [{@code method: Keyboard.down}] and [{@code method: Keyboard.up}].
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
*/
void press(String key, PressOptions options);
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
* <pre>{@code
* // Types instantly
* page.keyboard().type("Hello");
* // Types slower, like a user
* page.keyboard().type("World", new Keyboard.TypeOptions().setDelay(100));
* }</pre>
*
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.type}. Holding down {@code Shift} will not type the text in upper case.
*
* <p> <strong>NOTE:</strong> For characters that are not on a US keyboard, only an {@code input} event will be sent.
*
* @param text A text to type into a focused element.
*/
void press(String key, PressOptions delay);
default void type(String text) {
type(text, null);
}
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
* <pre>{@code
* // Types instantly
* page.keyboard().type("Hello");
* // Types slower, like a user
* page.keyboard().type("World", new Keyboard.TypeOptions().setDelay(100));
* }</pre>
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use [{@code method: Keyboard.press}].
*
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.type}. Holding down {@code Shift} will not type the text in upper case.
*
* <p> <strong>NOTE:</strong> For characters that are not on a US keyboard, only an {@code input} event will be sent.
*
* @param text A text to type into a focused element.
*/
void type(String text, TypeOptions options);
void type(String text, TypeOptions delay);
/**
* Dispatches a {@code keyup} event.
*
File diff suppressed because it is too large Load Diff
@@ -16,29 +16,21 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport.
*
* <p> Every {@code page} object has its own Mouse, accessible with {@link Page#mouse Page.mouse()}.
* <pre>{@code
* // Using page.mouse to trace a 100x100 square.
* page.mouse().move(0, 0);
* page.mouse().down();
* page.mouse().move(0, 100);
* page.mouse().move(100, 100);
* page.mouse().move(100, 0);
* page.mouse().move(0, 0);
* page.mouse().up();
* }</pre>
* <p> Every {@code page} object has its own Mouse, accessible with [{@code property: Page.mouse}].
*/
public interface Mouse {
enum Button { LEFT, MIDDLE, RIGHT }
class ClickOptions {
/**
* Defaults to {@code left}.
*/
public MouseButton button;
public Button button;
/**
* defaults to 1. See [UIEvent.detail].
*/
@@ -48,24 +40,15 @@ public interface Mouse {
*/
public Double delay;
/**
* Defaults to {@code left}.
*/
public ClickOptions setButton(MouseButton button) {
public ClickOptions withButton(Button button) {
this.button = button;
return this;
}
/**
* defaults to 1. See [UIEvent.detail].
*/
public ClickOptions setClickCount(int clickCount) {
public ClickOptions withClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public ClickOptions setDelay(double delay) {
public ClickOptions withDelay(double delay) {
this.delay = delay;
return this;
}
@@ -74,23 +57,17 @@ public interface Mouse {
/**
* Defaults to {@code left}.
*/
public MouseButton button;
public Button button;
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public Double delay;
/**
* Defaults to {@code left}.
*/
public DblclickOptions setButton(MouseButton button) {
public DblclickOptions withButton(Button button) {
this.button = button;
return this;
}
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public DblclickOptions setDelay(double delay) {
public DblclickOptions withDelay(double delay) {
this.delay = delay;
return this;
}
@@ -99,23 +76,17 @@ public interface Mouse {
/**
* Defaults to {@code left}.
*/
public MouseButton button;
public Button button;
/**
* defaults to 1. See [UIEvent.detail].
*/
public Integer clickCount;
/**
* Defaults to {@code left}.
*/
public DownOptions setButton(MouseButton button) {
public DownOptions withButton(Button button) {
this.button = button;
return this;
}
/**
* defaults to 1. See [UIEvent.detail].
*/
public DownOptions setClickCount(int clickCount) {
public DownOptions withClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
@@ -126,10 +97,7 @@ public interface Mouse {
*/
public Integer steps;
/**
* defaults to 1. Sends intermediate {@code mousemove} events.
*/
public MoveOptions setSteps(int steps) {
public MoveOptions withSteps(int steps) {
this.steps = steps;
return this;
}
@@ -138,52 +106,36 @@ public interface Mouse {
/**
* Defaults to {@code left}.
*/
public MouseButton button;
public Button button;
/**
* defaults to 1. See [UIEvent.detail].
*/
public Integer clickCount;
/**
* Defaults to {@code left}.
*/
public UpOptions setButton(MouseButton button) {
public UpOptions withButton(Button button) {
this.button = button;
return this;
}
/**
* defaults to 1. See [UIEvent.detail].
*/
public UpOptions setClickCount(int clickCount) {
public UpOptions withClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
}
/**
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}.
*/
default void click(double x, double y) {
click(x, y, null);
}
/**
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}.
* Shortcut for [{@code method: Mouse.move}], [{@code method: Mouse.down}], [{@code method: Mouse.up}].
*/
void click(double x, double y, ClickOptions options);
/**
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}, {@link
* Mouse#down Mouse.down()} and {@link Mouse#up Mouse.up()}.
*/
default void dblclick(double x, double y) {
dblclick(x, y, null);
}
/**
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}, {@link
* Mouse#down Mouse.down()} and {@link Mouse#up Mouse.up()}.
* Shortcut for [{@code method: Mouse.move}], [{@code method: Mouse.down}], [{@code method: Mouse.up}], [{@code method: Mouse.down}] and
* [{@code method: Mouse.up}].
*/
void dblclick(double x, double y, DblclickOptions options);
/**
* Dispatches a {@code mousedown} event.
*/
default void down() {
down(null);
}
@@ -191,9 +143,6 @@ public interface Mouse {
* Dispatches a {@code mousedown} event.
*/
void down(DownOptions options);
/**
* Dispatches a {@code mousemove} event.
*/
default void move(double x, double y) {
move(x, y, null);
}
@@ -201,9 +150,6 @@ public interface Mouse {
* Dispatches a {@code mousemove} event.
*/
void move(double x, double y, MoveOptions options);
/**
* Dispatches a {@code mouseup} event.
*/
default void up() {
up(null);
}
@@ -211,15 +157,5 @@ public interface Mouse {
* Dispatches a {@code mouseup} event.
*/
void up(UpOptions options);
/**
* Dispatches a {@code wheel} event.
*
* <p> <strong>NOTE:</strong> Wheel events may cause scrolling if they are not handled, and this method does not wait for the scrolling to finish
* before returning.
*
* @param deltaX Pixels to scroll horizontally.
* @param deltaY Pixels to scroll vertically.
*/
void wheel(double deltaX, double deltaY);
}
File diff suppressed because it is too large Load Diff
@@ -22,82 +22,34 @@ import java.util.*;
/**
* Playwright module provides a method to launch a browser instance. The following is a typical example of using Playwright
* to drive automation:
* <pre>{@code
* import com.microsoft.playwright.*;
*
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType chromium = playwright.chromium();
* Browser browser = chromium.launch();
* Page page = browser.newPage();
* page.navigate("http://example.com");
* // other actions...
* browser.close();
* }
* }
* }
* }</pre>
*/
public interface Playwright extends AutoCloseable {
class CreateOptions {
/**
* Additional environment variables that will be passed to the driver process. By default driver process inherits
* environment variables of the Playwright process.
*/
public Map<String, String> env;
/**
* Additional environment variables that will be passed to the driver process. By default driver process inherits
* environment variables of the Playwright process.
*/
public CreateOptions setEnv(Map<String, String> env) {
this.env = env;
return this;
}
}
public interface Playwright {
/**
* This object can be used to launch or connect to Chromium, returning instances of {@code Browser}.
* This object can be used to launch or connect to Chromium, returning instances of {@code ChromiumBrowser}.
*/
BrowserType chromium();
/**
* This object can be used to launch or connect to Firefox, returning instances of {@code Browser}.
* Returns a dictionary of devices to be used with [{@code method: Browser.newContext}] or [{@code method: Browser.newPage}].
*/
Map<String, DeviceDescriptor> devices();
/**
* This object can be used to launch or connect to Firefox, returning instances of {@code FirefoxBrowser}.
*/
BrowserType firefox();
/**
* Exposes API that can be used for the Web API testing.
*/
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.
* Selectors can be used to install custom selector engines. See
* [Working with selectors](./selectors.md#working-with-selectors) for more information.
*/
Selectors selectors();
/**
* This object can be used to launch or connect to WebKit, returning instances of {@code Browser}.
* This object can be used to launch or connect to WebKit, returning instances of {@code WebKitBrowser}.
*/
BrowserType webkit();
/**
* Terminates this instance of Playwright, will also close all created browsers if they are still running.
*/
void close();
/**
* Launches new Playwright driver process and connects to it. {@link Playwright#close Playwright.close()} should be called
* when the instance is no longer needed.
* <pre>{@code
* Playwright playwright = Playwright.create()) {
* Browser browser = playwright.webkit().launch();
* Page page = browser.newPage();
* page.navigate("https://www.w3.org/");
* playwright.close();
* }</pre>
*/
static Playwright create(CreateOptions options) {
return PlaywrightImpl.create(options);
}
static Playwright create() {
return create(null);
return PlaywrightImpl.create();
}
void close() throws Exception;
}
@@ -16,10 +16,6 @@
package com.microsoft.playwright;
/**
* PlaywrightException is thrown whenever certain operations are terminated abnormally, e.g. browser closes while {@link
* Page#evaluate Page.evaluate()} is running. All Playwright exceptions inherit from this class.
*/
public class PlaywrightException extends RuntimeException {
public PlaywrightException(String message) {
super(message);
@@ -14,14 +14,26 @@
* limitations under the License.
*/
package com.microsoft.playwright.options;
package com.microsoft.playwright;
public class Position {
public double x;
public double y;
public int x;
public int y;
public Position(double x, double y) {
public Position() {
}
public Position(int x, int y) {
this.x = x;
this.y = y;
}
}
public Position withX(int x) {
this.x = x;
return this;
}
public Position withY(int y) {
this.y = y;
return this;
}
}
@@ -16,64 +16,125 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* Whenever the page sends a request for a network resource the following sequence of events are emitted by {@code Page}:
* <ul>
* <li> {@link Page#onRequest Page.onRequest()} emitted when the request is issued by the page.</li>
* <li> {@link Page#onResponse Page.onResponse()} emitted when/if the response status and headers are received for the request.</li>
* <li> {@link Page#onRequestFinished Page.onRequestFinished()} emitted when the response body is downloaded and the request is
* complete.</li>
* </ul>
* - [{@code event: Page.request}] emitted when the request is issued by the page.
* - [{@code event: Page.response}] emitted when/if the response status and headers are received for the request.
* - [{@code event: Page.requestfinished}] emitted when the response body is downloaded and the request is complete.
*
* <p> If request fails at some point, then instead of {@code "requestfinished"} event (and possibly instead of 'response' event),
* the {@link Page#onRequestFailed Page.onRequestFailed()} event is emitted.
* <p> If request fails at some point, then instead of {@code 'requestfinished'} event (and possibly instead of 'response' event),
* the [{@code event: Page.requestfailed}] event is emitted.
*
* <p> <strong>NOTE:</strong> HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will complete
* with {@code "requestfinished"} event.
* <p> <strong>NOTE:</strong> HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will
* complete with {@code 'requestfinished'} event.
*
* <p> If request gets a 'redirect' response, the request is successfully finished with the 'requestfinished' event, and a new
* request is issued to a redirected url.
*/
public interface Request {
/**
* An object with all the request HTTP headers associated with this request. The header names are lower-cased.
*/
Map<String, String> allHeaders();
class RequestFailure {
/**
* Human-readable error message, e.g. {@code 'net::ERR_FAILED'}.
*/
private String errorText;
public RequestFailure(String errorText) {
this.errorText = errorText;
}
public String errorText() {
return this.errorText;
}
}
class RequestTiming {
/**
* Request start time in milliseconds elapsed since January 1, 1970 00:00:00 UTC
*/
private double startTime;
/**
* Time immediately before the browser starts the domain name lookup for the resource. The value is given in milliseconds
* relative to {@code startTime}, -1 if not available.
*/
private double domainLookupStart;
/**
* Time immediately after the browser starts the domain name lookup for the resource. The value is given in milliseconds
* relative to {@code startTime}, -1 if not available.
*/
private double domainLookupEnd;
/**
* Time immediately before the user agent starts establishing the connection to the server to retrieve the resource. The
* value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private double connectStart;
/**
* Time immediately before the browser starts the handshake process to secure the current connection. The value is given in
* milliseconds relative to {@code startTime}, -1 if not available.
*/
private double secureConnectionStart;
/**
* Time immediately before the user agent starts establishing the connection to the server to retrieve the resource. The
* value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private double connectEnd;
/**
* Time immediately before the browser starts requesting the resource from the server, cache, or local resource. The value
* is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private double requestStart;
/**
* Time immediately after the browser starts requesting the resource from the server, cache, or local resource. The value
* is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private double responseStart;
/**
* Time immediately after the browser receives the last byte of the resource or immediately before the transport connection
* is closed, whichever comes first. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private double responseEnd;
public double startTime() {
return this.startTime;
}
public double domainLookupStart() {
return this.domainLookupStart;
}
public double domainLookupEnd() {
return this.domainLookupEnd;
}
public double connectStart() {
return this.connectStart;
}
public double secureConnectionStart() {
return this.secureConnectionStart;
}
public double connectEnd() {
return this.connectEnd;
}
public double requestStart() {
return this.requestStart;
}
public double responseStart() {
return this.responseStart;
}
public double responseEnd() {
return this.responseEnd;
}
}
/**
* The method returns {@code null} unless this request has failed, as reported by {@code requestfailed} event.
*
* <p> Example of logging of all the failed requests:
* <pre>{@code
* page.onRequestFailed(request -> {
* System.out.println(request.url() + " " + request.failure());
* });
* }</pre>
*/
String failure();
RequestFailure failure();
/**
* Returns the {@code Frame} that initiated this request.
*/
Frame frame();
/**
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Request#allHeaders
* Request.allHeaders()} instead.
* An object with HTTP headers associated with the request. All header names are lower-case.
*/
Map<String, String> headers();
/**
* An array with all the request HTTP headers associated with this request. Unlike {@link Request#allHeaders
* Request.allHeaders()}, header names are NOT lower-cased. Headers with multiple entries, such as {@code Set-Cookie}, appear in
* the array multiple times.
*/
List<HttpHeader> headersArray();
/**
* Returns the value of the header matching the name. The name is case insensitive.
*
* @param name Name of the header.
*/
String headerValue(String name);
/**
* Whether this request is driving frame's navigation.
*/
@@ -98,25 +159,14 @@ public interface Request {
* construct the whole redirect chain by repeatedly calling {@code redirectedFrom()}.
*
* <p> For example, if the website {@code http://example.com} redirects to {@code https://example.com}:
* <pre>{@code
* Response response = page.navigate("http://example.com");
* System.out.println(response.request().redirectedFrom().url()); // "http://example.com"
* }</pre>
*
* <p> If the website {@code https://google.com} has no redirects:
* <pre>{@code
* Response response = page.navigate("https://google.com");
* System.out.println(response.request().redirectedFrom()); // null
* }</pre>
*/
Request redirectedFrom();
/**
* New request issued by the browser if the server responded with redirect.
*
* <p> This method is the opposite of {@link Request#redirectedFrom Request.redirectedFrom()}:
* <pre>{@code
* System.out.println(request.redirectedFrom().redirectedTo() == request); // true
* }</pre>
* <p> This method is the opposite of [{@code method: Request.redirectedFrom}]:
*/
Request redirectedTo();
/**
@@ -129,23 +179,12 @@ public interface Request {
* Returns the matching {@code Response} object, or {@code null} if the response was not received due to error.
*/
Response response();
/**
* Returns resource size information for given request.
*/
Sizes sizes();
/**
* Returns resource timing information for given request. Most of the timing values become available upon the response,
* {@code responseEnd} becomes available when request finishes. Find more information at <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming">Resource Timing API</a>.
* <pre>{@code
* page.onRequestFinished(request -> {
* Timing timing = request.timing();
* System.out.println(timing.responseEnd - timing.startTime);
* });
* page.navigate("http://example.com");
* }</pre>
* {@code responseEnd} becomes available when request finishes. Find more information at
* [Resource Timing API](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming).
*/
Timing timing();
RequestTiming timing();
/**
* URL of the request.
*/
@@ -16,23 +16,18 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* {@code Response} class represents responses which are received by page.
*/
public interface Response {
/**
* An object with all the response HTTP headers associated with this response.
*/
Map<String, String> allHeaders();
/**
* Returns the buffer with response body.
*/
byte[] body();
/**
* Waits for this response to finish, returns always {@code null}.
* Waits for this response to finish, returns failure error if request failed.
*/
String finished();
/**
@@ -40,30 +35,9 @@ public interface Response {
*/
Frame frame();
/**
* **DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use {@link Response#allHeaders
* Response.allHeaders()} instead.
* Returns the object with HTTP headers associated with the response. All header names are lower-case.
*/
Map<String, String> headers();
/**
* An array with all the request HTTP headers associated with this response. Unlike {@link Response#allHeaders
* Response.allHeaders()}, header names are NOT lower-cased. Headers with multiple entries, such as {@code Set-Cookie}, appear in
* the array multiple times.
*/
List<HttpHeader> headersArray();
/**
* Returns the value of the header matching the name. The name is case insensitive. If multiple headers have the same name
* (except {@code set-cookie}), they are returned as a list separated by {@code , }. For {@code set-cookie}, the {@code \n} separator is used. If
* no headers are found, {@code null} is returned.
*
* @param name Name of the header.
*/
String headerValue(String name);
/**
* Returns all values of the headers matching the name, for example {@code set-cookie}. The name is case insensitive.
*
* @param name Name of the header.
*/
List<String> headerValues(String name);
/**
* Contains a boolean stating whether the response was successful (status in the range 200-299) or not.
*/
@@ -72,14 +46,6 @@ public interface Response {
* Returns the matching {@code Request} object.
*/
Request request();
/**
* Returns SSL and other security information.
*/
SecurityDetails securityDetails();
/**
* Returns the IP address and port of the server.
*/
ServerAddr serverAddr();
/**
* Contains the status code of the response (e.g., 200 for a success).
*/
@@ -16,15 +16,16 @@
package com.microsoft.playwright;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
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.
* Whenever a network route is set up with [{@code method: Page.route}] or [{@code method: BrowserContext.route}], the {@code Route} object
* allows to handle the route.
*/
public interface Route {
class ResumeOptions {
class ContinueOptions {
/**
* If set changes the request HTTP headers. Header values will be converted to a string.
*/
@@ -36,56 +37,38 @@ public interface Route {
/**
* If set changes the post data of request
*/
public Object postData;
public byte[] postData;
/**
* If set changes the request URL. New URL must have same protocol as original one.
*/
public String url;
/**
* If set changes the request HTTP headers. Header values will be converted to a string.
*/
public ResumeOptions setHeaders(Map<String, String> headers) {
public ContinueOptions withHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
/**
* If set changes the request method (e.g. GET or POST)
*/
public ResumeOptions setMethod(String method) {
public ContinueOptions withMethod(String method) {
this.method = method;
return this;
}
/**
* If set changes the post data of request
*/
public ResumeOptions setPostData(String postData) {
public ContinueOptions withPostData(String postData) {
this.postData = postData.getBytes(StandardCharsets.UTF_8);
return this;
}
public ContinueOptions withPostData(byte[] postData) {
this.postData = postData;
return this;
}
/**
* If set changes the post data of request
*/
public ResumeOptions setPostData(byte[] postData) {
this.postData = postData;
return this;
}
/**
* If set changes the request URL. New URL must have same protocol as original one.
*/
public ResumeOptions setUrl(String url) {
public ContinueOptions withUrl(String url) {
this.url = url;
return this;
}
}
class FulfillOptions {
/**
* Optional response body as text.
* Response body.
*/
public String body;
/**
* Optional response body as raw bytes.
*/
public byte[] bodyBytes;
/**
* If set, equals to setting {@code Content-Type} response header.
@@ -100,71 +83,36 @@ public interface Route {
* is resolved relative to the current working directory.
*/
public Path path;
/**
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be overridden
* using fulfill options.
*/
public APIResponse response;
/**
* Response status code, defaults to {@code 200}.
*/
public Integer status;
/**
* Optional response body as text.
*/
public FulfillOptions setBody(String body) {
public FulfillOptions withBody(byte[] body) {
this.bodyBytes = body;
return this;
}
public FulfillOptions withBody(String body) {
this.body = body;
return this;
}
/**
* Optional response body as raw bytes.
*/
public FulfillOptions setBodyBytes(byte[] bodyBytes) {
this.bodyBytes = bodyBytes;
return this;
}
/**
* If set, equals to setting {@code Content-Type} response header.
*/
public FulfillOptions setContentType(String contentType) {
public FulfillOptions withContentType(String contentType) {
this.contentType = contentType;
return this;
}
/**
* Response headers. Header values will be converted to a string.
*/
public FulfillOptions setHeaders(Map<String, String> headers) {
public FulfillOptions withHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
/**
* File path to respond with. The content type will be inferred from file extension. If {@code path} is a relative path, then it
* is resolved relative to the current working directory.
*/
public FulfillOptions setPath(Path path) {
public FulfillOptions withPath(Path path) {
this.path = path;
return this;
}
/**
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be overridden
* using fulfill options.
*/
public FulfillOptions setResponse(APIResponse response) {
this.response = response;
return this;
}
/**
* Response status code, defaults to {@code 200}.
*/
public FulfillOptions setStatus(int status) {
public FulfillOptions withStatus(int status) {
this.status = status;
return this;
}
}
/**
* Aborts the route's request.
*/
default void abort() {
abort(null);
}
@@ -172,94 +120,36 @@ public interface Route {
* Aborts the route's request.
*
* @param errorCode Optional error code. Defaults to {@code failed}, could be one of the following:
* <ul>
* <li> {@code "aborted"} - An operation was aborted (due to user action)</li>
* <li> {@code "accessdenied"} - Permission to access a resource, other than the network, was denied</li>
* <li> {@code "addressunreachable"} - The IP address is unreachable. This usually means that there is no route to the specified host
* or network.</li>
* <li> {@code "blockedbyclient"} - The client chose to block the request.</li>
* <li> {@code "blockedbyresponse"} - The request failed because the response was delivered along with requirements which are not met
* ('X-Frame-Options' and 'Content-Security-Policy' ancestor checks, for instance).</li>
* <li> {@code "connectionaborted"} - A connection timed out as a result of not receiving an ACK for data sent.</li>
* <li> {@code "connectionclosed"} - A connection was closed (corresponding to a TCP FIN).</li>
* <li> {@code "connectionfailed"} - A connection attempt failed.</li>
* <li> {@code "connectionrefused"} - A connection attempt was refused.</li>
* <li> {@code "connectionreset"} - A connection was reset (corresponding to a TCP RST).</li>
* <li> {@code "internetdisconnected"} - The Internet connection has been lost.</li>
* <li> {@code "namenotresolved"} - The host name could not be resolved.</li>
* <li> {@code "timedout"} - An operation timed out.</li>
* <li> {@code "failed"} - A generic failure occurred.</li>
* </ul>
* - {@code 'aborted'} - An operation was aborted (due to user action)
* - {@code 'accessdenied'} - Permission to access a resource, other than the network, was denied
* - {@code 'addressunreachable'} - The IP address is unreachable. This usually means that there is no route to the specified
* host or network.
* - {@code 'blockedbyclient'} - The client chose to block the request.
* - {@code 'blockedbyresponse'} - The request failed because the response was delivered along with requirements which are not
* met ('X-Frame-Options' and 'Content-Security-Policy' ancestor checks, for instance).
* - {@code 'connectionaborted'} - A connection timed out as a result of not receiving an ACK for data sent.
* - {@code 'connectionclosed'} - A connection was closed (corresponding to a TCP FIN).
* - {@code 'connectionfailed'} - A connection attempt failed.
* - {@code 'connectionrefused'} - A connection attempt was refused.
* - {@code 'connectionreset'} - A connection was reset (corresponding to a TCP RST).
* - {@code 'internetdisconnected'} - The Internet connection has been lost.
* - {@code 'namenotresolved'} - The host name could not be resolved.
* - {@code 'timedout'} - An operation timed out.
* - {@code 'failed'} - A generic failure occurred.
*/
void abort(String errorCode);
/**
* Continues route's request with optional overrides.
* <pre>{@code
* page.route("**\/*", route -> {
* // Override headers
* Map<String, String> headers = new HashMap<>(route.request().headers());
* headers.put("foo", "bar"); // set "foo" header
* headers.remove("origin"); // remove "origin" header
* route.resume(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*/
default void resume() {
resume(null);
default void continue_() {
continue_(null);
}
/**
* Continues route's request with optional overrides.
* <pre>{@code
* page.route("**\/*", route -> {
* // Override headers
* Map<String, String> headers = new HashMap<>(route.request().headers());
* headers.put("foo", "bar"); // set "foo" header
* headers.remove("origin"); // remove "origin" header
* route.resume(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*/
void resume(ResumeOptions options);
/**
* Fulfills route's request with given response.
*
* <p> An example of fulfilling all requests with 404 responses:
* <pre>{@code
* page.route("**\/*", route -> {
* route.fulfill(new Route.FulfillOptions()
* .setStatus(404)
* .setContentType("text/plain")
* .setBody("Not Found!"));
* });
* }</pre>
*
* <p> An example of serving static file:
* <pre>{@code
* page.route("**\/xhr_endpoint", route -> route.fulfill(
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json"))));
* }</pre>
*/
void continue_(ContinueOptions options);
default void fulfill() {
fulfill(null);
}
/**
* Fulfills route's request with given response.
*
* <p> An example of fulfilling all requests with 404 responses:
* <pre>{@code
* page.route("**\/*", route -> {
* route.fulfill(new Route.FulfillOptions()
* .setStatus(404)
* .setContentType("text/plain")
* .setBody("Not Found!"));
* });
* }</pre>
*
* <p> An example of serving static file:
* <pre>{@code
* page.route("**\/xhr_endpoint", route -> route.fulfill(
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json"))));
* }</pre>
*/
void fulfill(FulfillOptions options);
/**
@@ -17,10 +17,11 @@
package com.microsoft.playwright;
import java.nio.file.Path;
import java.util.*;
/**
* 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.
* Selectors can be used to install custom selector engines. See
* [Working with selectors](./selectors.md#working-with-selectors) for more information.
*/
public interface Selectors {
class RegisterOptions {
@@ -31,151 +32,22 @@ public interface Selectors {
*/
public Boolean contentScript;
/**
* Whether to run this selector engine in isolated JavaScript environment. This environment has access to the same DOM, but
* not any JavaScript objects from the frame's scripts. Defaults to {@code false}. Note that running as a content script is not
* guaranteed when this engine is used together with other registered engines.
*/
public RegisterOptions setContentScript(boolean contentScript) {
public RegisterOptions withContentScript(boolean contentScript) {
this.contentScript = contentScript;
return this;
}
}
/**
* An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance.
* String createTagNameEngine = "{\n" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
* " return root.querySelector(selector);\n" +
* " },\n" +
* " // Returns all elements matching given selector in the root's subtree.\n" +
* " queryAll(root, selector) {\n" +
* " return Array.from(root.querySelectorAll(selector));\n" +
* " }\n" +
* "}";
* // Register the engine. Selectors will be prefixed with "tag=".
* playwright.selectors().register("tag", createTagNameEngine);
* Browser browser = playwright.firefox().launch();
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
* }</pre>
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance.
*/
default void register(String name, String script) {
register(name, script, null);
}
/**
* An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance.
* String createTagNameEngine = "{\n" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
* " return root.querySelector(selector);\n" +
* " },\n" +
* " // Returns all elements matching given selector in the root's subtree.\n" +
* " queryAll(root, selector) {\n" +
* " return Array.from(root.querySelectorAll(selector));\n" +
* " }\n" +
* "}";
* // Register the engine. Selectors will be prefixed with "tag=".
* playwright.selectors().register("tag", createTagNameEngine);
* Browser browser = playwright.firefox().launch();
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
* }</pre>
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance.
*/
default void register(String name, String script) { register(name, script, null); }
void register(String name, String script, RegisterOptions options);
default void register(String name, Path path) { register(name, path, null); }
/**
* An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance.
* String createTagNameEngine = "{\n" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
* " return root.querySelector(selector);\n" +
* " },\n" +
* " // Returns all elements matching given selector in the root's subtree.\n" +
* " queryAll(root, selector) {\n" +
* " return Array.from(root.querySelectorAll(selector));\n" +
* " }\n" +
* "}";
* // Register the engine. Selectors will be prefixed with "tag=".
* playwright.selectors().register("tag", createTagNameEngine);
* Browser browser = playwright.firefox().launch();
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
* }</pre>
*
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance.
*/
default void register(String name, Path script) {
register(name, script, null);
}
/**
* An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance.
* String createTagNameEngine = "{\n" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
* " return root.querySelector(selector);\n" +
* " },\n" +
* " // Returns all elements matching given selector in the root's subtree.\n" +
* " queryAll(root, selector) {\n" +
* " return Array.from(root.querySelectorAll(selector));\n" +
* " }\n" +
* "}";
* // Register the engine. Selectors will be prefixed with "tag=".
* playwright.selectors().register("tag", createTagNameEngine);
* Browser browser = playwright.firefox().launch();
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.click("tag=div >> text=\"Click me\"");
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
* }</pre>
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance.
*/
void register(String name, Path script, RegisterOptions options);
void register(String name, Path path, RegisterOptions options);
}
@@ -16,17 +16,14 @@
package com.microsoft.playwright;
/**
* TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. {@link Page#waitForSelector
* Page.waitForSelector()} or {@link BrowserType#launch BrowserType.launch()}.
*/
public class TimeoutError extends PlaywrightException {
public TimeoutError(String message) {
super(message);
}
import java.util.*;
public TimeoutError(String message, Throwable exception) {
super(message, exception);
}
/**
* - extends: [Error]
*
* <p> TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. [{@code method: Page.waitForSelector}]
* or [{@code method: BrowserType.launch}].
*/
public interface TimeoutError {
}
@@ -16,10 +16,11 @@
package com.microsoft.playwright;
import java.util.*;
/**
* The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the
* touchscreen can only be used in browser contexts that have been initialized with {@code hasTouch} set to true.
* touchscreen can only be used in browser contexts that have been intialized with {@code hasTouch} set to true.
*/
public interface Touchscreen {
/**
@@ -1,254 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import 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.
*
* <p> Start recording a trace before performing actions. At the end, stop tracing and save it to a file.
* <pre>{@code
* Browser browser = chromium.launch();
* BrowserContext context = browser.newContext();
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
* context.tracing().stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*/
public interface Tracing {
class StartOptions {
/**
* If specified, the trace is going to be saved into the file with the given name inside the {@code tracesDir} folder specified
* in {@link BrowserType#launch BrowserType.launch()}.
*/
public String name;
/**
* Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview.
*/
public Boolean screenshots;
/**
* If this option is true tracing will
* <ul>
* <li> capture DOM snapshot on every action</li>
* <li> record network activity</li>
* </ul>
*/
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.
*/
public Boolean sources;
/**
* Trace name to be shown in the Trace Viewer.
*/
public String title;
/**
* If specified, the trace is going to be saved into the file with the given name inside the {@code tracesDir} folder specified
* in {@link BrowserType#launch BrowserType.launch()}.
*/
public StartOptions setName(String name) {
this.name = name;
return this;
}
/**
* Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview.
*/
public StartOptions setScreenshots(boolean screenshots) {
this.screenshots = screenshots;
return this;
}
/**
* If this option is true tracing will
* <ul>
* <li> capture DOM snapshot on every action</li>
* <li> record network activity</li>
* </ul>
*/
public StartOptions setSnapshots(boolean snapshots) {
this.snapshots = snapshots;
return this;
}
/**
* 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.
*/
public StartOptions setSources(boolean sources) {
this.sources = sources;
return this;
}
/**
* Trace name to be shown in the Trace Viewer.
*/
public StartOptions setTitle(String title) {
this.title = title;
return this;
}
}
class StartChunkOptions {
/**
* Trace name to be shown in the Trace Viewer.
*/
public String title;
/**
* Trace name to be shown in the Trace Viewer.
*/
public StartChunkOptions setTitle(String title) {
this.title = title;
return this;
}
}
class StopOptions {
/**
* Export trace into the file with the given path.
*/
public Path path;
/**
* Export trace into the file with the given path.
*/
public StopOptions setPath(Path path) {
this.path = path;
return this;
}
}
class StopChunkOptions {
/**
* Export trace collected since the last {@link Tracing#startChunk Tracing.startChunk()} call into the file with the given
* path.
*/
public Path path;
/**
* Export trace collected since the last {@link Tracing#startChunk Tracing.startChunk()} call into the file with the given
* path.
*/
public StopChunkOptions setPath(Path path) {
this.path = path;
return this;
}
}
/**
* Start tracing.
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
* context.tracing().stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*/
default void start() {
start(null);
}
/**
* Start tracing.
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
* context.tracing().stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*/
void start(StartOptions options);
/**
* Start a new trace chunk. If you'd like to record multiple traces on the same {@code BrowserContext}, use {@link Tracing#start
* Tracing.start()} once, and then create multiple trace chunks with {@link Tracing#startChunk Tracing.startChunk()} and
* {@link Tracing#stopChunk Tracing.stopChunk()}.
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
*
* context.tracing().startChunk();
* page.click("text=Get Started");
* // Everything between startChunk and stopChunk will be recorded in the trace.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace1.zip")));
*
* context.tracing().startChunk();
* page.navigate("http://example.com");
* // Save a second trace file with different actions.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace2.zip")));
* }</pre>
*/
default void startChunk() {
startChunk(null);
}
/**
* Start a new trace chunk. If you'd like to record multiple traces on the same {@code BrowserContext}, use {@link Tracing#start
* Tracing.start()} once, and then create multiple trace chunks with {@link Tracing#startChunk Tracing.startChunk()} and
* {@link Tracing#stopChunk Tracing.stopChunk()}.
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
*
* context.tracing().startChunk();
* page.click("text=Get Started");
* // Everything between startChunk and stopChunk will be recorded in the trace.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace1.zip")));
*
* context.tracing().startChunk();
* page.navigate("http://example.com");
* // Save a second trace file with different actions.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace2.zip")));
* }</pre>
*/
void startChunk(StartChunkOptions options);
/**
* Stop tracing.
*/
default void stop() {
stop(null);
}
/**
* Stop tracing.
*/
void stop(StopOptions options);
/**
* Stop the trace chunk. See {@link Tracing#startChunk Tracing.startChunk()} for more details about multiple trace chunks.
*/
default void stopChunk() {
stopChunk(null);
}
/**
* Stop the trace chunk. See {@link Tracing#startChunk Tracing.startChunk()} for more details about multiple trace chunks.
*/
void stopChunk(StopChunkOptions options);
}
@@ -17,29 +17,16 @@
package com.microsoft.playwright;
import java.nio.file.Path;
import java.util.*;
/**
* When browser context is created with the {@code recordVideo} option, each page has a video object associated with it.
* <pre>{@code
* System.out.println(page.video().path());
* }</pre>
* When browser context is created with the {@code videosPath} option, each page has a video object associated with it.
*/
public interface Video {
/**
* Deletes the video file. Will wait for the video to finish if necessary.
*/
void delete();
/**
* Returns the file system path this video will be recorded to. The video is guaranteed to be written to the filesystem
* upon closing the browser context. This method throws when connected remotely.
* upon closing the browser context.
*/
Path path();
/**
* Saves the video to a user-specified path. It is safe to call this method while the video is still in progress, or after
* the page has closed. This method waits until the page is closed and the video is fully saved.
*
* @param path Path where the video should be saved.
*/
void saveAs(Path path);
}
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -23,97 +24,55 @@ import java.util.function.Predicate;
* The {@code WebSocket} class represents websocket connections in the page.
*/
public interface WebSocket {
interface FrameData {
byte[] body();
String text();
}
/**
* Fired when the websocket closes.
*/
void onClose(Consumer<WebSocket> handler);
/**
* Removes handler that was previously added with {@link #onClose onClose(handler)}.
*/
void offClose(Consumer<WebSocket> handler);
/**
* Fired when the websocket receives a frame.
*/
void onFrameReceived(Consumer<WebSocketFrame> handler);
/**
* Removes handler that was previously added with {@link #onFrameReceived onFrameReceived(handler)}.
*/
void offFrameReceived(Consumer<WebSocketFrame> handler);
void onFrameReceived(Consumer<FrameData> handler);
void offFrameReceived(Consumer<FrameData> handler);
/**
* Fired when the websocket sends a frame.
*/
void onFrameSent(Consumer<WebSocketFrame> handler);
/**
* Removes handler that was previously added with {@link #onFrameSent onFrameSent(handler)}.
*/
void offFrameSent(Consumer<WebSocketFrame> handler);
void onFrameSent(Consumer<FrameData> handler);
void offFrameSent(Consumer<FrameData> handler);
/**
* Fired when the websocket has an error.
*/
void onSocketError(Consumer<String> handler);
/**
* Removes handler that was previously added with {@link #onSocketError onSocketError(handler)}.
*/
void offSocketError(Consumer<String> handler);
class WaitForFrameReceivedOptions {
/**
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
*/
public Predicate<WebSocketFrame> predicate;
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*/
public Double timeout;
/**
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
*/
public WaitForFrameReceivedOptions setPredicate(Predicate<WebSocketFrame> predicate) {
this.predicate = predicate;
return this;
}
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*/
public WaitForFrameReceivedOptions setTimeout(double timeout) {
public WaitForFrameReceivedOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
FrameData waitForFrameReceived(Runnable code, WaitForFrameReceivedOptions options);
default FrameData waitForFrameReceived(Runnable code) { return waitForFrameReceived(code, null); }
class WaitForFrameSentOptions {
/**
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
*/
public Predicate<WebSocketFrame> predicate;
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*/
public Double timeout;
/**
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
*/
public WaitForFrameSentOptions setPredicate(Predicate<WebSocketFrame> predicate) {
this.predicate = predicate;
return this;
}
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*/
public WaitForFrameSentOptions setTimeout(double timeout) {
public WaitForFrameSentOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
FrameData waitForFrameSent(Runnable code, WaitForFrameSentOptions options);
default FrameData waitForFrameSent(Runnable code) { return waitForFrameSent(code, null); }
class WaitForSocketErrorOptions {
public Double timeout;
public WaitForSocketErrorOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
String waitForSocketError(Runnable code, WaitForSocketErrorOptions options);
default String waitForSocketError(Runnable code) { return waitForSocketError(code, null); }
/**
* Indicates that the web socket has been closed.
*/
@@ -122,41 +81,5 @@ public interface WebSocket {
* Contains the URL of the WebSocket.
*/
String url();
/**
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
* WebSocket or Page is closed before the frame is received.
*
* @param callback Callback that performs the action triggering the event.
*/
default WebSocketFrame waitForFrameReceived(Runnable callback) {
return waitForFrameReceived(null, callback);
}
/**
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
* WebSocket or Page is closed before the frame is received.
*
* @param callback Callback that performs the action triggering the event.
*/
WebSocketFrame waitForFrameReceived(WaitForFrameReceivedOptions options, Runnable callback);
/**
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
* WebSocket or Page is closed before the frame is sent.
*
* @param callback Callback that performs the action triggering the event.
*/
default WebSocketFrame waitForFrameSent(Runnable callback) {
return waitForFrameSent(null, callback);
}
/**
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
* WebSocket or Page is closed before the frame is sent.
*
* @param callback Callback that performs the action triggering the event.
*/
WebSocketFrame waitForFrameSent(WaitForFrameSentOptions options, Runnable callback);
}
@@ -1,35 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
/**
* The {@code WebSocketFrame} class represents frames sent over {@code WebSocket} connections in the page. Frame payload is returned by
* either {@link WebSocketFrame#text WebSocketFrame.text()} or {@link WebSocketFrame#binary WebSocketFrame.binary()} method
* depending on the its type.
*/
public interface WebSocketFrame {
/**
* Returns binary payload.
*/
byte[] binary();
/**
* Returns text payload.
*/
String text();
}
@@ -16,128 +16,63 @@
package com.microsoft.playwright;
import java.util.*;
import java.util.function.Consumer;
/**
* The Worker class represents a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">WebWorker</a>.
* {@code worker} event is emitted on the page object to signal a worker creation. {@code close} event is emitted on the worker object
* when the worker is gone.
* <pre>{@code
* page.onWorker(worker -> {
* System.out.println("Worker created: " + worker.url());
* worker.onClose(worker1 -> System.out.println("Worker destroyed: " + worker1.url()));
* });
* System.out.println("Current workers:");
* for (Worker worker : page.workers())
* System.out.println(" " + worker.url());
* }</pre>
* The Worker class represents a [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API). {@code worker}
* event is emitted on the page object to signal a worker creation. {@code close} event is emitted on the worker object when the
* worker is gone.
*/
public interface Worker {
/**
* Emitted when this dedicated <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">WebWorker</a> is
* terminated.
*/
void onClose(Consumer<Worker> handler);
/**
* Removes handler that was previously added with {@link #onClose onClose(handler)}.
*/
void offClose(Consumer<Worker> handler);
class WaitForCloseOptions {
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*/
public Double timeout;
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*/
public WaitForCloseOptions setTimeout(double timeout) {
class WaitForCloseOptions {
public Double timeout;
public WaitForCloseOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
/**
* Returns the return value of {@code expression}.
*
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluate Worker.evaluate()} would wait for the promise to resolve and return its value.
*
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a non-[Serializable] value, then {@link
* Worker#evaluate Worker.evaluate()} returns {@code undefined}. Playwright also supports transferring some additional values
* that are not serializable by {@code JSON}: {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}.
*
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
*/
default Object evaluate(String expression) {
return evaluate(expression, null);
Worker waitForClose(Runnable code, WaitForCloseOptions options);
default Worker waitForClose(Runnable code) { return waitForClose(code, null); }
default Object evaluate(String pageFunction) {
return evaluate(pageFunction, null);
}
/**
* Returns the return value of {@code expression}.
* Returns the return value of {@code pageFunction}
*
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluate Worker.evaluate()} would wait for the promise to resolve and return its value.
* <p> If the function passed to the {@code worker.evaluate} returns a [Promise], then {@code worker.evaluate} would wait for the promise
* to resolve and return its value.
*
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a non-[Serializable] value, then {@link
* Worker#evaluate Worker.evaluate()} returns {@code undefined}. Playwright also supports transferring some additional values
* that are not serializable by {@code JSON}: {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}.
* <p> If the function passed to the {@code worker.evaluate} returns a non-[Serializable] value, then {@code worker.evaluate} returns
* {@code undefined}. DevTools Protocol also supports transferring some additional values that are not serializable by {@code JSON}:
* {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}, and bigint literals.
*
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
* @param arg Optional argument to pass to {@code expression}.
* @param pageFunction Function to be evaluated in the worker context
* @param arg Optional argument to pass to {@code pageFunction}
*/
Object evaluate(String expression, Object arg);
/**
* Returns the return value of {@code expression} as a {@code JSHandle}.
*
* <p> The only difference between {@link Worker#evaluate Worker.evaluate()} and {@link Worker#evaluateHandle
* Worker.evaluateHandle()} is that {@link Worker#evaluateHandle Worker.evaluateHandle()} returns {@code JSHandle}.
*
* <p> If the function passed to the {@link Worker#evaluateHandle Worker.evaluateHandle()} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluateHandle Worker.evaluateHandle()} would wait for the promise to resolve and return its value.
*
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
*/
default JSHandle evaluateHandle(String expression) {
return evaluateHandle(expression, null);
Object evaluate(String pageFunction, Object arg);
default JSHandle evaluateHandle(String pageFunction) {
return evaluateHandle(pageFunction, null);
}
/**
* Returns the return value of {@code expression} as a {@code JSHandle}.
* Returns the return value of {@code pageFunction} as in-page object (JSHandle).
*
* <p> The only difference between {@link Worker#evaluate Worker.evaluate()} and {@link Worker#evaluateHandle
* Worker.evaluateHandle()} is that {@link Worker#evaluateHandle Worker.evaluateHandle()} returns {@code JSHandle}.
* <p> The only difference between {@code worker.evaluate} and {@code worker.evaluateHandle} is that {@code worker.evaluateHandle} returns
* in-page object (JSHandle).
*
* <p> If the function passed to the {@link Worker#evaluateHandle Worker.evaluateHandle()} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluateHandle Worker.evaluateHandle()} would wait for the promise to resolve and return its value.
* <p> If the function passed to the {@code worker.evaluateHandle} returns a [Promise], then {@code worker.evaluateHandle} would wait for
* the promise to resolve and return its value.
*
* @param expression JavaScript expression to be evaluated in the browser context. If it looks like a function declaration, it is interpreted
* as a function. Otherwise, evaluated as an expression.
* @param arg Optional argument to pass to {@code expression}.
* @param pageFunction Function to be evaluated in the page context
* @param arg Optional argument to pass to {@code pageFunction}
*/
JSHandle evaluateHandle(String expression, Object arg);
JSHandle evaluateHandle(String pageFunction, Object arg);
String url();
/**
* Performs action and waits for the Worker to close.
*
* @param callback Callback that performs the action triggering the event.
*/
default Worker waitForClose(Runnable callback) {
return waitForClose(null, callback);
}
/**
* Performs action and waits for the Worker to close.
*
* @param callback Callback that performs the action triggering the event.
*/
Worker waitForClose(WaitForCloseOptions options, Runnable callback);
}
@@ -1,56 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.assertions;
/**
* The {@code APIResponseAssertions} class provides assertion methods that can be used to make assertions about the {@code APIResponse}
* in the tests. A new instance of {@code APIResponseAssertions} is created by calling {@link PlaywrightAssertions#assertThat
* PlaywrightAssertions.assertThat()}:
* <pre>{@code
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestPage {
* ...
* @Test
* void navigatesToLoginPage() {
* ...
* APIResponse response = page.request().get('https://playwright.dev');
* assertThat(response).isOK();
* }
* }
* }</pre>
*/
public interface APIResponseAssertions {
/**
* Makes the assertion check for the opposite condition. For example, this code tests that the response status is not
* successful:
* <pre>{@code
* assertThat(response).not().isOK();
* }</pre>
*/
APIResponseAssertions not();
/**
* Ensures the response status code is within [200..299] range.
* <pre>{@code
* assertThat(response).isOK();
* }</pre>
*/
void isOK();
}
@@ -1,158 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.assertions;
import java.util.regex.Pattern;
/**
* The {@code PageAssertions} class provides assertion methods that can be used to make assertions about the {@code Page} state in the
* tests. A new instance of {@code PageAssertions} is created by calling {@link PlaywrightAssertions#assertThat
* PlaywrightAssertions.assertThat()}:
* <pre>{@code
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestPage {
* ...
* @Test
* void navigatesToLoginPage() {
* ...
* page.click("#login");
* assertThat(page).hasURL(Pattern.compile(".*\/login"));
* }
* }
* }</pre>
*/
public interface PageAssertions {
class HasTitleOptions {
/**
* Time to retry the assertion for.
*/
public Double timeout;
/**
* Time to retry the assertion for.
*/
public HasTitleOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class HasURLOptions {
/**
* Time to retry the assertion for.
*/
public Double timeout;
/**
* Time to retry the assertion for.
*/
public HasURLOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
/**
* Makes the assertion check for the opposite condition. For example, this code tests that the page URL doesn't contain
* {@code "error"}:
* <pre>{@code
* assertThat(page).not().hasURL("error");
* }</pre>
*/
PageAssertions not();
/**
* Ensures the page has the given title.
* <pre>{@code
* assertThat(page).hasTitle("Playwright");
* }</pre>
*
* @param titleOrRegExp Expected title or RegExp.
*/
default void hasTitle(String titleOrRegExp) {
hasTitle(titleOrRegExp, null);
}
/**
* Ensures the page has the given title.
* <pre>{@code
* assertThat(page).hasTitle("Playwright");
* }</pre>
*
* @param titleOrRegExp Expected title or RegExp.
*/
void hasTitle(String titleOrRegExp, HasTitleOptions options);
/**
* Ensures the page has the given title.
* <pre>{@code
* assertThat(page).hasTitle("Playwright");
* }</pre>
*
* @param titleOrRegExp Expected title or RegExp.
*/
default void hasTitle(Pattern titleOrRegExp) {
hasTitle(titleOrRegExp, null);
}
/**
* Ensures the page has the given title.
* <pre>{@code
* assertThat(page).hasTitle("Playwright");
* }</pre>
*
* @param titleOrRegExp Expected title or RegExp.
*/
void hasTitle(Pattern titleOrRegExp, HasTitleOptions options);
/**
* Ensures the page is navigated to the given URL.
* <pre>{@code
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected substring or RegExp.
*/
default void hasURL(String urlOrRegExp) {
hasURL(urlOrRegExp, null);
}
/**
* Ensures the page is navigated to the given URL.
* <pre>{@code
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected substring or RegExp.
*/
void hasURL(String urlOrRegExp, HasURLOptions options);
/**
* Ensures the page is navigated to the given URL.
* <pre>{@code
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected substring or RegExp.
*/
default void hasURL(Pattern urlOrRegExp) {
hasURL(urlOrRegExp, null);
}
/**
* Ensures the page is navigated to the given URL.
* <pre>{@code
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected substring or RegExp.
*/
void hasURL(Pattern urlOrRegExp, HasURLOptions options);
}
@@ -1,90 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.assertions;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.impl.APIResponseAssertionsImpl;
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.
*
* <p> Consider the following example:
* <pre>{@code
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestExample {
* ...
* @Test
* void statusBecomesSubmitted() {
* ...
* page.click("#submit-button");
* assertThat(page.locator(".status")).hasText("Submitted");
* }
* }
* }</pre>
*
* <p> Playwright will be re-testing the node with the selector {@code .status} until fetched Node has the {@code "Submitted"} text. It
* will be re-fetching the node and checking it over and over, until the condition is met or until the timeout is reached.
* You can pass this timeout as an option.
*
* <p> By default, the timeout for assertions is set to 5 seconds.
*/
public interface PlaywrightAssertions {
/**
* Creates a {@code APIResponseAssertions} object for the given {@code APIResponse}.
* <pre>{@code
* PlaywrightAssertions.assertThat(response).isOK();
* }</pre>
*
* @param response {@code APIResponse} object to use for assertions.
*/
static APIResponseAssertions assertThat(APIResponse response) {
return new APIResponseAssertionsImpl(response);
}
/**
* Creates a {@code LocatorAssertions} object for the given {@code Locator}.
* <pre>{@code
* PlaywrightAssertions.assertThat(locator).isVisible();
* }</pre>
*
* @param locator {@code Locator} object to use for assertions.
*/
static LocatorAssertions assertThat(Locator locator) {
return new LocatorAssertionsImpl(locator);
}
/**
* Creates a {@code PageAssertions} object for the given {@code Page}.
* <pre>{@code
* PlaywrightAssertions.assertThat(page).hasTitle("News");
* }</pre>
*
* @param page {@code Page} object to use for assertions.
*/
static PageAssertions assertThat(Page page) {
return new PageAssertionsImpl(page);
}
}
@@ -1,199 +0,0 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequestContext;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Request;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.RequestOptions;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.toFilePayload;
class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
private final TracingImpl tracing;
APIRequestContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
}
@Override
public APIResponse delete(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "DELETE"));
}
@Override
public void dispose() {
withLogging("APIRequestContext.dispose", () -> sendMessage("dispose"));
}
@Override
public APIResponse fetch(String urlOrRequest, RequestOptions options) {
return withLogging("APIRequestContext.fetch", () -> fetchImpl(urlOrRequest, (RequestOptionsImpl) options));
}
@Override
public APIResponse fetch(Request request, RequestOptions optionsArg) {
RequestOptionsImpl options = (RequestOptionsImpl) optionsArg;
if (options == null) {
options = new RequestOptionsImpl();
}
if (options.method == null) {
options.method = request.method();
}
if (options.headers == null) {
options.headers = request.headers();
}
if (options.data == null && options.form == null && options.multipart == null) {
options.data = request.postDataBuffer();
}
return fetch(request.url(), options);
}
private APIResponse fetchImpl(String url, RequestOptionsImpl options) {
if (options == null) {
options = new RequestOptionsImpl();
}
JsonObject params = new JsonObject();
params.addProperty("url", url);
if (options.params != null) {
Map<String, String> queryParams = new LinkedHashMap<>();
for (Map.Entry<String, ?> e : options.params.entrySet()) {
queryParams.put(e.getKey(), "" + e.getValue());
}
params.add("params", toNameValueArray(queryParams));
}
if (options.method != null) {
params.addProperty("method", options.method);
}
if (options.headers != null) {
params.add("headers", toProtocol(options.headers));
}
if (options.data != null) {
byte[] bytes = null;
if (options.data instanceof byte[]) {
bytes = (byte[]) options.data;
} else if (options.data instanceof String && !isJsonContentType(options.headers)) {
bytes = ((String) options.data).getBytes(StandardCharsets.UTF_8);
}
if (bytes == null) {
params.add("jsonData", gson().toJsonTree(options.data));
} else {
String base64 = Base64.getEncoder().encodeToString(bytes);
params.addProperty("postData", base64);
}
}
if (options.form != null) {
params.add("formData", toNameValueArray(options.form.fields));
}
if (options.multipart != null) {
params.add("multipartData", serializeMultipartData(options.multipart.fields));
}
if (options.timeout != null) {
params.addProperty("timeout", options.timeout);
}
if (options.failOnStatusCode != null) {
params.addProperty("failOnStatusCode", options.failOnStatusCode);
}
if (options.ignoreHTTPSErrors != null) {
params.addProperty("ignoreHTTPSErrors", options.ignoreHTTPSErrors);
}
JsonObject json = sendMessage("fetch", params).getAsJsonObject();
return new APIResponseImpl(this, json.getAsJsonObject("response"));
}
private static boolean isJsonContentType(Map<String, String> headers) {
if (headers == null) {
return false;
}
for (Map.Entry<String, String> e : headers.entrySet()) {
if ("content-type".equalsIgnoreCase(e.getKey())) {
return "application/json".equals(e.getValue());
}
}
return false;
}
private static JsonArray serializeMultipartData(Map<String, Object> data) {
JsonArray result = new JsonArray();
for (Map.Entry<String, Object> e : data.entrySet()) {
FilePayload filePayload = null;
if (e.getValue() instanceof FilePayload) {
filePayload = (FilePayload) e.getValue();
} else if (e.getValue() instanceof Path) {
filePayload = toFilePayload((Path) e.getValue());
} else if (e.getValue() instanceof File) {
filePayload = toFilePayload(((File) e.getValue()).toPath());
}
JsonObject item = new JsonObject();
item.addProperty("name", e.getKey());
if (filePayload == null) {
item.addProperty("value", "" + e.getValue());
} else {
item.add("file", toProtocol(filePayload));
}
result.add(item);
}
return result;
}
@Override
public APIResponse get(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "GET"));
}
@Override
public APIResponse head(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "HEAD"));
}
@Override
public APIResponse patch(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "PATCH"));
}
@Override
public APIResponse post(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "POST"));
}
@Override
public APIResponse put(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "PUT"));
}
@Override
public String storageState(StorageStateOptions options) {
return withLogging("APIRequestContext.storageState", () -> {
JsonElement json = sendMessage("storageState");
String storageState = json.toString();
if (options != null && options.path != null) {
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
}
return storageState;
});
}
private static RequestOptionsImpl ensureOptions(RequestOptions options, String method) {
RequestOptionsImpl impl = (RequestOptionsImpl) options;
if (impl == null) {
impl = new RequestOptionsImpl();
}
if (impl.method == null) {
impl.method = method;
}
return impl;
}
}
@@ -1,53 +0,0 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.PlaywrightException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import static com.microsoft.playwright.impl.Serialization.gson;
class APIRequestImpl implements APIRequest {
private final PlaywrightImpl playwright;
APIRequestImpl(PlaywrightImpl playwright) {
this.playwright = playwright;
}
@Override
public APIRequestContextImpl newContext(NewContextOptions options) {
return playwright.withLogging("APIRequest.newContext", () -> newContextImpl(options));
}
private APIRequestContextImpl newContextImpl(NewContextOptions options) {
if (options == null) {
options = new NewContextOptions();
}
if (options.storageStatePath != null) {
try {
byte[] bytes = Files.readAllBytes(options.storageStatePath);
options.storageState = new String(bytes, StandardCharsets.UTF_8);
options.storageStatePath = null;
} catch (IOException e) {
throw new PlaywrightException("Failed to read storage state from file", e);
}
}
JsonObject storageState = null;
if (options.storageState != null) {
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
options.storageState = null;
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (storageState != null) {
params.add("storageState", storageState);
}
JsonObject result = playwright.sendMessage("newRequest", params).getAsJsonObject();
APIRequestContextImpl context = playwright.connection.getExistingObject(result.getAsJsonObject("request").get("guid").getAsString());
return context;
}
}
@@ -1,59 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.assertions.APIResponseAssertions;
import org.opentest4j.AssertionFailedError;
import java.util.List;
public class APIResponseAssertionsImpl implements APIResponseAssertions {
private final APIResponse actual;
private final boolean isNot;
APIResponseAssertionsImpl(APIResponse response, boolean isNot) {
this.actual = response;
this.isNot = isNot;
}
public APIResponseAssertionsImpl(APIResponse response) {
this(response, false);
}
@Override
public APIResponseAssertions not() {
return new APIResponseAssertionsImpl(actual, !isNot);
}
@Override
public void isOK() {
if (actual.ok() == !isNot) {
return;
}
String message = "Response status expected to be within [200..299] range, was " + actual.status();
if (isNot) {
message = message.replace("expected to", "expected not to");
}
List<String> logList = ((APIResponseImpl) actual).fetchLog();
String log = String.join("\n", logList);
if (!log.isEmpty()) {
log = "\nCall log:\n" + log;
}
throw new AssertionFailedError(message + log);
}
}
@@ -1,124 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.HttpHeader;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static java.util.Arrays.asList;
class APIResponseImpl implements APIResponse {
final APIRequestContextImpl context;
private final JsonObject initializer;
private final RawHeaders headers;
APIResponseImpl(APIRequestContextImpl apiRequestContext, JsonObject response) {
context = apiRequestContext;
initializer = response;
headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
}
@Override
public byte[] body() {
return context.withLogging("APIResponse.body", () -> {
try {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchResponseBody", params).getAsJsonObject();
if (!json.has("binary")) {
throw new PlaywrightException("Response has been disposed");
}
return Base64.getDecoder().decode(json.get("binary").getAsString());
} catch (PlaywrightException e) {
if (isSafeCloseError(e)) {
throw new PlaywrightException("Response has been disposed");
}
throw e;
}
});
}
@Override
public void dispose() {
context.withLogging("APIResponse.dispose", () -> {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
context.sendMessage("disposeAPIResponse", params);
});
}
@Override
public Map<String, String> headers() {
return headers.headers();
}
@Override
public List<HttpHeader> headersArray() {
return headers.headersArray();
}
@Override
public boolean ok() {
int status = status();
return status == 0 || (status >= 200 && status <= 299);
}
@Override
public int status() {
return initializer.get("status").getAsInt();
}
@Override
public String statusText() {
return initializer.get("statusText").getAsString();
}
@Override
public String text() {
return new String(body(), StandardCharsets.UTF_8);
}
@Override
public String url() {
return initializer.get("url").getAsString();
}
String fetchUid() {
return initializer.get("fetchUid").getAsString();
}
List<String> fetchLog() {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchLog", params).getAsJsonObject();
JsonArray log = json.get("log").getAsJsonArray();
return gson().fromJson(log, new TypeToken<List<String>>() {}.getType());
}
}
@@ -0,0 +1,48 @@
/*
* 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 com.google.gson.JsonObject;
import com.microsoft.playwright.Accessibility;
import com.microsoft.playwright.AccessibilityNode;
import static com.microsoft.playwright.impl.Serialization.gson;
class AccessibilityImpl implements Accessibility {
private final PageImpl page;
AccessibilityImpl(PageImpl page) {
this.page = page;
}
@Override
public AccessibilityNode snapshot(SnapshotOptions options) {
return page.withLogging("Accessibility.snapshot", () -> snapshotImpl(options));
}
private AccessibilityNode snapshotImpl(SnapshotOptions options) {
if (options == null) {
options = new SnapshotOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject json = page.sendMessage("accessibilitySnapshot", params).getAsJsonObject();
if (!json.has("rootAXNode")) {
return null;
}
return new AccessibilityNodeImpl(json.getAsJsonObject("rootAXNode"));
}
}
@@ -0,0 +1,258 @@
/*
* 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 com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.AccessibilityNode;
import java.util.ArrayList;
import java.util.List;
class AccessibilityNodeImpl implements AccessibilityNode {
private final JsonObject json;
AccessibilityNodeImpl(JsonObject json) {
this.json = json;
}
@Override
public String role() {
return json.get("role").getAsString();
}
@Override
public String name() {
return json.get("name").getAsString();
}
@Override
public String valueString() {
if (!json.has("valueString")) {
return null;
}
return json.get("valueString").getAsString();
}
@Override
public Double valueNumber() {
if (!json.has("valueNumber")) {
return null;
}
return json.get("valueNumber").getAsDouble();
}
@Override
public String description() {
if (!json.has("description")) {
return null;
}
return json.get("description").getAsString();
}
@Override
public String keyshortcuts() {
if (!json.has("keyshortcuts")) {
return null;
}
return json.get("keyshortcuts").getAsString();
}
@Override
public String roledescription() {
if (!json.has("roledescription")) {
return null;
}
return json.get("roledescription").getAsString();
}
@Override
public String valuetext() {
if (!json.has("valuetext")) {
return null;
}
return json.get("valuetext").getAsString();
}
@Override
public Boolean disabled() {
if (!json.has("disabled")) {
return null;
}
return json.get("disabled").getAsBoolean();
}
@Override
public Boolean expanded() {
if (!json.has("expanded")) {
return null;
}
return json.get("expanded").getAsBoolean();
}
@Override
public Boolean focused() {
if (!json.has("focused")) {
return null;
}
return json.get("focused").getAsBoolean();
}
@Override
public Boolean modal() {
if (!json.has("modal")) {
return null;
}
return json.get("modal").getAsBoolean();
}
@Override
public Boolean multiline() {
if (!json.has("multiline")) {
return null;
}
return json.get("multiline").getAsBoolean();
}
@Override
public Boolean multiselectable() {
if (!json.has("multiselectable")) {
return null;
}
return json.get("multiselectable").getAsBoolean();
}
@Override
public Boolean readonly() {
if (!json.has("readonly")) {
return null;
}
return json.get("readonly").getAsBoolean();
}
@Override
public Boolean required() {
if (!json.has("required")) {
return null;
}
return json.get("required").getAsBoolean();
}
@Override
public Boolean selected() {
if (!json.has("selected")) {
return null;
}
return json.get("selected").getAsBoolean();
}
@Override
public CheckedState checked() {
if (!json.has("checked")) {
return null;
}
String value = json.get("checked").getAsString();
switch (value) {
case "checked": return CheckedState.CHECKED;
case "unchecked": return CheckedState.UNCHECKED;
case "mixed": return CheckedState.MIXED;
default: throw new IllegalStateException("Unexpected value: " + value);
}
}
@Override
public PressedState pressed() {
if (!json.has("pressed")) {
return null;
}
String value = json.get("pressed").getAsString();
switch (value) {
case "pressed": return PressedState.PRESSED;
case "released": return PressedState.RELEASED;
case "mixed": return PressedState.MIXED;
default: throw new IllegalStateException("Unexpected value: " + value);
}
}
@Override
public Integer level() {
if (!json.has("level")) {
return null;
}
return json.get("level").getAsInt();
}
@Override
public Double valuemin() {
if (!json.has("valuemin")) {
return null;
}
return json.get("valuemin").getAsDouble();
}
@Override
public Double valuemax() {
if (!json.has("valuemax")) {
return null;
}
return json.get("valuemax").getAsDouble();
}
@Override
public String autocomplete() {
if (!json.has("autocomplete")) {
return null;
}
return json.get("autocomplete").getAsString();
}
@Override
public String haspopup() {
if (!json.has("haspopup")) {
return null;
}
return json.get("haspopup").getAsString();
}
@Override
public String invalid() {
if (!json.has("invalid")) {
return null;
}
return json.get("invalid").getAsString();
}
@Override
public String orientation() {
if (!json.has("orientation")) {
return null;
}
return json.get("orientation").getAsString();
}
@Override
public List<AccessibilityNode> children() {
if (!json.has("children")) {
return null;
}
List<AccessibilityNode> result = new ArrayList<>();
for (JsonElement e : json.getAsJsonArray("children")) {
result.add(new AccessibilityNodeImpl(e.getAsJsonObject()));
}
return result;
}
}
@@ -1,79 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import static com.microsoft.playwright.impl.Utils.writeToFile;
class ArtifactImpl extends ChannelOwner {
boolean isRemote;
public ArtifactImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
public InputStream createReadStream() {
JsonObject result = sendMessage("stream").getAsJsonObject();
if (!result.has("stream")) {
return null;
}
Stream stream = connection.getExistingObject(result.getAsJsonObject("stream").get("guid").getAsString());
return stream.stream();
}
public void cancel() {
sendMessage("cancel");
}
public void delete() {
sendMessage("delete");
}
public String failure() {
JsonObject result = sendMessage("failure").getAsJsonObject();
if (result.has("error")) {
return result.get("error").getAsString();
}
return null;
}
public Path pathAfterFinished() {
if (isRemote) {
throw new PlaywrightException("Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy.");
}
JsonObject json = sendMessage("pathAfterFinished").getAsJsonObject();
return FileSystems.getDefault().getPath(json.get("value").getAsString());
}
public void saveAs(Path path) {
if (isRemote) {
JsonObject jsonObject = sendMessage("saveAsStream").getAsJsonObject();
Stream stream = connection.getExistingObject(jsonObject.getAsJsonObject("stream").get("guid").getAsString());
writeToFile(stream.stream(), path);
return;
}
JsonObject params = new JsonObject();
params.addProperty("path", path.toString());
sendMessage("saveAs", params);
}
}
@@ -1,94 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import org.opentest4j.AssertionFailedError;
import org.opentest4j.ValueWrapper;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
import static java.util.Arrays.asList;
class AssertionsBase {
final LocatorImpl actualLocator;
final boolean isNot;
AssertionsBase(LocatorImpl actual, boolean isNot) {
this.actualLocator = actual;
this.isNot = isNot;
}
void expectImpl(String expression, ExpectedTextValue textValue, Object expected, String message, FrameExpectOptions options) {
expectImpl(expression, asList(textValue), expected, message, options);
}
void expectImpl(String expression, List<ExpectedTextValue> expectedText, Object expected, String message, FrameExpectOptions options) {
if (options == null) {
options = new FrameExpectOptions();
}
options.expectedText = expectedText;
options.isNot = isNot;
expectImpl(expression, options, expected, message);
}
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message) {
if (expectOptions.timeout == null) {
expectOptions.timeout = 5_000.0;
}
if (expectOptions.isNot) {
message = message.replace("expected to", "expected not to");
}
FrameExpectResult result = actualLocator.expect(expression, expectOptions);
if (result.matches == isNot) {
Object actual = result.received == null ? null : Serialization.deserialize(result.received);
String log = String.join("\n", result.log);
if (!log.isEmpty()) {
log = "\nCall log:\n" + log;
}
if (expected == null) {
throw new AssertionFailedError(message + log);
}
ValueWrapper expectedValue = formatValue(expected);
ValueWrapper actualValue = formatValue(actual);
message += ": " + expectedValue.getStringRepresentation() + "\nReceived: " + actualValue.getStringRepresentation() + "\n";
throw new AssertionFailedError(message + log, expectedValue, actualValue);
}
}
private static ValueWrapper formatValue(Object value) {
if (value == null || !value.getClass().isArray()) {
return ValueWrapper.create(value);
}
Collection<String> values = asList((Object[]) value).stream().map(e -> e.toString()).collect(Collectors.toList());
String stringRepresentation = "[" + String.join(", ", values) + "]";
return ValueWrapper.create(value, stringRepresentation);
}
static ExpectedTextValue expectedRegex(Pattern pattern) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.regexSource = pattern.pattern();
if (pattern.flags() != 0) {
expected.regexFlags = toJsRegexFlags(pattern);
}
return expected;
}
}
@@ -22,7 +22,6 @@ import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.BindingCallback;
import java.util.ArrayList;
import java.util.List;
@@ -30,7 +29,7 @@ import java.util.List;
import static com.microsoft.playwright.impl.Serialization.*;
class BindingCall extends ChannelOwner {
private static class SourceImpl implements BindingCallback.Source {
private static class SourceImpl implements Page.Binding.Source {
private final Frame frame;
public SourceImpl(Frame frame) {
@@ -61,10 +60,10 @@ class BindingCall extends ChannelOwner {
return initializer.get("name").getAsString();
}
void call(BindingCallback binding) {
void call(Page.Binding binding) {
try {
Frame frame = connection.getExistingObject(initializer.getAsJsonObject("frame").get("guid").getAsString());
BindingCallback.Source source = new SourceImpl(frame);
Page.Binding.Source source = new SourceImpl(frame);
List<Object> args = new ArrayList<>();
if (initializer.has("handle")) {
JSHandle handle = connection.getExistingObject(initializer.getAsJsonObject("handle").get("guid").getAsString());
@@ -20,15 +20,10 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.BindingCallback;
import com.microsoft.playwright.options.Cookie;
import com.microsoft.playwright.options.FunctionCallback;
import com.microsoft.playwright.options.Geolocation;
import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
@@ -36,33 +31,23 @@ 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.isFunctionBody;
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;
class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final BrowserImpl browser;
private final TracingImpl tracing;
private final APIRequestContextImpl request;
final List<PageImpl> pages = new ArrayList<>();
final Router routes = new Router();
private boolean isClosedOrClosing;
final Map<String, BindingCallback> bindings = new HashMap<>();
final Map<String, Page.Binding> bindings = new HashMap<>();
PageImpl ownerPage;
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
final TimeoutSettings timeoutSettings = new TimeoutSettings();
Path videosDir;
URL baseUrl;
Path recordHarPath;
enum EventType {
CLOSE,
PAGE,
REQUEST,
REQUESTFAILED,
REQUESTFINISHED,
RESPONSE,
}
BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@@ -72,17 +57,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} else {
browser = null;
}
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
tracing.isRemote = browser != null && browser.isRemote;
this.request = connection.getExistingObject(initializer.getAsJsonObject("APIRequestContext").get("guid").getAsString());
}
void setBaseUrl(String spec) {
try {
this.baseUrl = new URL(spec);
} catch (MalformedURLException e) {
this.baseUrl = null;
}
}
@Override
@@ -105,94 +79,32 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.remove(EventType.PAGE, handler);
}
@Override
public void onRequest(Consumer<Request> handler) {
listeners.add(EventType.REQUEST, handler);
}
@Override
public void offRequest(Consumer<Request> handler) {
listeners.remove(EventType.REQUEST, handler);
}
@Override
public void onRequestFailed(Consumer<Request> handler) {
listeners.add(EventType.REQUESTFAILED, handler);
}
@Override
public void offRequestFailed(Consumer<Request> handler) {
listeners.remove(EventType.REQUESTFAILED, handler);
}
@Override
public void onRequestFinished(Consumer<Request> handler) {
listeners.add(EventType.REQUESTFINISHED, handler);
}
@Override
public void offRequestFinished(Consumer<Request> handler) {
listeners.remove(EventType.REQUESTFINISHED, handler);
}
@Override
public void onResponse(Consumer<Response> handler) {
listeners.add(EventType.RESPONSE, handler);
}
@Override
public void offResponse(Consumer<Response> handler) {
listeners.remove(EventType.RESPONSE, handler);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Predicate<T> predicate, Double timeout) {
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType, predicate));
waitables.add(new WaitableEvent<>(listeners, eventType));
waitables.add(new WaitableContextClose<>());
waitables.add(timeoutSettings.createWaitable(timeout));
return runUntil(code, new WaitableRace<>(waitables));
}
@Override
public Page waitForPage(WaitForPageOptions options, Runnable code) {
return withWaitLogging("BrowserContext.close", () -> waitForPageImpl(options, code));
}
private Page waitForPageImpl(WaitForPageOptions options, Runnable code) {
public Page waitForPage(Runnable code, WaitForPageOptions options) {
if (options == null) {
options = new WaitForPageOptions();
}
return waitForEventWithTimeout(EventType.PAGE, code, options.predicate, options.timeout);
return waitForEventWithTimeout(EventType.PAGE, code, options.timeout);
}
@Override
public void close() {
withLogging("BrowserContext.close", () -> closeImpl());
}
@Override
public List<Cookie> cookies(String url) {
return cookies(url == null ? new ArrayList<>() : Collections.singletonList(url));
}
private void closeImpl() {
if (isClosedOrClosing) {
return;
}
isClosedOrClosing = true;
try {
if (recordHarPath != null) {
JsonObject json = sendMessage("harExport").getAsJsonObject();
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
// In case of CDP connection browser is null but since the connection is established by
// the driver it is safe to consider the artifact local.
if (browser() != null && browser().isRemote) {
artifact.isRemote = true;
}
artifact.saveAs(recordHarPath);
artifact.delete();
}
sendMessage("close");
} catch (PlaywrightException e) {
if (!isSafeCloseError(e)) {
@@ -202,7 +114,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void addCookies(List<Cookie> cookies) {
public void addCookies(List<AddCookie> cookies) {
withLogging("BrowserContext.addCookies", () -> {
JsonObject params = new JsonObject();
params.add("cookies", gson().toJsonTree(cookies));
@@ -211,24 +123,15 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void addInitScript(String script) {
withLogging("BrowserContext.addInitScript", () -> addInitScriptImpl(script));
public void addInitScript(String script, Object arg) {
withLogging("BrowserContext.addInitScript", () -> addInitScriptImpl(script, arg));
}
@Override
public void addInitScript(Path path) {
withLogging("BrowserContext.addInitScript", () -> {
try {
byte[] bytes = readAllBytes(path);
addInitScriptImpl(new String(bytes, UTF_8));
} catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e);
}
});
}
private void addInitScriptImpl(String script) {
private void addInitScriptImpl(String script, Object arg) {
// TODO: serialize arg
JsonObject params = new JsonObject();
if (isFunctionBody(script)) {
script = "(" + script + ")()";
}
params.addProperty("source", script);
sendMessage("addInitScript", params);
}
@@ -256,20 +159,20 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private List<Cookie> cookiesImpl(List<String> urls) {
JsonObject params = new JsonObject();
if (urls == null) {
urls = new ArrayList<>();
urls = Collections.emptyList();
}
params.add("urls", gson().toJsonTree(urls));
JsonObject json = sendMessage("cookies", params).getAsJsonObject();
Cookie[] cookies = gson().fromJson(json.getAsJsonArray("cookies"), Cookie[].class);
return asList(cookies);
return Arrays.asList(cookies);
}
@Override
public void exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
public void exposeBinding(String name, Page.Binding playwrightBinding, ExposeBindingOptions options) {
withLogging("BrowserContext.exposeBinding", () -> exposeBindingImpl(name, playwrightBinding, options));
}
private void exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
private void exposeBindingImpl(String name, Page.Binding playwrightBinding, ExposeBindingOptions options) {
if (bindings.containsKey(name)) {
throw new PlaywrightException("Function \"" + name + "\" has been already registered");
}
@@ -289,9 +192,9 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void exposeFunction(String name, FunctionCallback playwrightFunction) {
public void exposeFunction(String name, Page.Function playwrightFunction) {
withLogging("BrowserContext.exposeFunction",
() -> exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null));
() -> exposeBindingImpl(name, (Page.Binding.Source source, Object... args) -> playwrightFunction.call(args), null));
}
@Override
@@ -304,7 +207,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
options = new GrantPermissionsOptions();
}
if (permissions == null) {
permissions = new ArrayList<>();
permissions = Collections.emptyList();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.add("permissions", gson().toJsonTree(permissions));
@@ -330,28 +233,23 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public APIRequestContextImpl request() {
return request;
public void route(String url, Consumer<Route> handler) {
route(new UrlMatcher(url), handler);
}
@Override
public void route(String url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(this.baseUrl, url), handler, options);
public void route(Pattern url, Consumer<Route> handler) {
route(new UrlMatcher(url), handler);
}
@Override
public void route(Pattern url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(url), handler, options);
public void route(Predicate<String> url, Consumer<Route> handler) {
route(new UrlMatcher(url), handler);
}
@Override
public void route(Predicate<String> url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(url), handler, options);
}
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
private void route(UrlMatcher matcher, Consumer<Route> handler) {
withLogging("BrowserContext.route", () -> {
routes.add(matcher, handler, options == null ? null : options.times);
routes.add(matcher, handler);
if (routes.size() == 1) {
JsonObject params = new JsonObject();
params.addProperty("enabled", true);
@@ -417,25 +315,27 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public String storageState(StorageStateOptions options) {
public StorageState storageState(StorageStateOptions options) {
return withLogging("BrowserContext.storageState", () -> {
JsonElement json = sendMessage("storageState");
String storageState = json.toString();
StorageState storageState = gson().fromJson(json, StorageState.class);
if (options != null && options.path != null) {
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
try {
Files.createDirectories(options.path.getParent());
try (FileWriter writer = new FileWriter(options.path.toFile())) {
writer.write(json.toString());
}
} catch (IOException e) {
throw new PlaywrightException("Failed to write storage state to file", e);
}
}
return storageState;
});
}
@Override
public TracingImpl tracing() {
return tracing;
}
@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(new UrlMatcher(this.baseUrl, url), handler);
unroute(new UrlMatcher(url), handler);
}
@Override
@@ -462,102 +362,38 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
withLogging("BrowserContext.unroute", () -> {
routes.remove(matcher, handler);
maybeDisableNetworkInterception();
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
});
}
private void maybeDisableNetworkInterception() {
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
}
void handleRoute(Route route) {
boolean handled = routes.handle(route);
if (handled) {
maybeDisableNetworkInterception();
} else {
route.resume();
}
}
void pause() {
sendMessage("pause");
}
@Override
protected void handleEvent(String event, JsonObject params) {
if ("route".equals(event)) {
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
handleRoute(route);
boolean handled = routes.handle(route);
if (!handled) {
route.continue_();
}
} else if ("page".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
pages.add(page);
listeners.notify(EventType.PAGE, page);
if (page.opener() != null && !page.opener().isClosed()) {
page.opener().notifyPopup(page);
}
pages.add(page);
} else if ("bindingCall".equals(event)) {
BindingCall bindingCall = connection.getExistingObject(params.getAsJsonObject("binding").get("guid").getAsString());
BindingCallback binding = bindings.get(bindingCall.name());
Page.Binding binding = bindings.get(bindingCall.name());
if (binding != null) {
bindingCall.call(binding);
}
} else if ("request".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
listeners.notify(EventType.REQUEST, request);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.REQUEST, request);
}
} else if ("requestFailed".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
request.didFailOrFinish = true;
if (params.has("failureText")) {
request.failure = params.get("failureText").getAsString();
}
if (request.timing != null) {
request.timing.responseEnd = params.get("responseEndTiming").getAsDouble();
}
listeners.notify(EventType.REQUESTFAILED, request);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.REQUESTFAILED, request);
}
} else if ("requestFinished".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
request.didFailOrFinish = true;
if (request.timing != null) {
request.timing.responseEnd = params.get("responseEndTiming").getAsDouble();
}
listeners.notify(EventType.REQUESTFINISHED, request);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.REQUESTFINISHED, request);
}
} else if ("response".equals(event)) {
String guid = params.getAsJsonObject("response").get("guid").getAsString();
Response response = connection.getExistingObject(guid);
listeners.notify(EventType.RESPONSE, response);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.RESPONSE, response);
}
} else if ("close".equals(event)) {
didClose();
isClosedOrClosing = true;
if (browser != null) {
browser.contexts.remove(this);
}
listeners.notify(EventType.CLOSE, this);
}
}
void didClose() {
isClosedOrClosing = true;
if (browser != null) {
browser.contexts.remove(this);
}
listeners.notify(EventType.CLOSE, this);
}
}
@@ -16,31 +16,27 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.*;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
class BrowserImpl extends ChannelOwner implements Browser {
final Set<BrowserContextImpl> contexts = new HashSet<>();
final Set<BrowserContext> contexts = new HashSet<>();
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
boolean isRemote;
boolean isConnectedOverWebSocket;
private boolean isConnected = true;
LocalUtils localUtils;
enum EventType {
DISCONNECTED,
@@ -64,16 +60,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
public void close() {
withLogging("Browser.close", () -> closeImpl());
}
private void closeImpl() {
if (isConnectedOverWebSocket) {
try {
connection.close();
} catch (IOException e) {
throw new PlaywrightException("Failed to close browser connection", e);
}
return;
}
try {
sendMessage("close");
} catch (PlaywrightException e) {
@@ -83,17 +70,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
}
void notifyRemoteClosed() {
// Emulate all pages, contexts and the browser closing upon disconnect.
for (BrowserContextImpl context : new ArrayList<>(contexts)) {
for (PageImpl page : new ArrayList<>(context.pages)) {
page.didClose();
}
context.didClose();
}
didClose();
}
@Override
public List<BrowserContext> contexts() {
return new ArrayList<>(contexts);
@@ -114,65 +90,19 @@ class BrowserImpl extends ChannelOwner implements Browser {
options = new NewContextOptions();
}
if (options.storageStatePath != null) {
try {
byte[] bytes = Files.readAllBytes(options.storageStatePath);
options.storageState = new String(bytes, StandardCharsets.UTF_8);
try (FileReader reader = new FileReader(options.storageStatePath.toFile())) {
options.storageState = gson().fromJson(reader, BrowserContext.StorageState.class);
options.storageStatePath = null;
} catch (IOException e) {
throw new PlaywrightException("Failed to read storage state from file", e);
}
}
JsonObject storageState = null;
if (options.storageState != null) {
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
options.storageState = null;
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (storageState != null) {
params.add("storageState", storageState);
}
if (options.recordHarPath != null) {
JsonObject recordHar = new JsonObject();
recordHar.addProperty("path", options.recordHarPath.toString());
if (options.recordHarOmitContent != null) {
recordHar.addProperty("omitContent", true);
}
params.remove("recordHarPath");
params.remove("recordHarOmitContent");
params.add("recordHar", recordHar);
} else if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toString());
if (options.recordVideoSize != null) {
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
}
params.remove("recordVideoDir");
params.remove("recordVideoSize");
params.add("recordVideo", recordVideo);
} else if (options.recordVideoSize != null) {
throw new PlaywrightException("recordVideoSize is set but recordVideoDir is null");
}
if (options.viewportSize != null) {
if (options.viewportSize.isPresent()) {
JsonElement size = params.get("viewportSize");
params.remove("viewportSize");
params.add("viewport", size);
} else {
params.remove("viewportSize");
params.addProperty("noDefaultViewport", true);
}
}
JsonElement result = sendMessage("newContext", params);
BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
context.videosDir = options.recordVideoDir;
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
if (options.recordVideo != null) {
context.videosDir = options.recordVideo.dir;
}
context.recordHarPath = options.recordHarPath;
context.tracing().localUtils = localUtils;
contexts.add(context);
return context;
}
@@ -182,36 +112,8 @@ class BrowserImpl extends ChannelOwner implements Browser {
return withLogging("Browser.newPage", () -> newPageImpl(options));
}
@Override
public void startTracing(Page page, StartTracingOptions options) {
withLogging("Browser.startTracing", () -> startTracingImpl(page, options));
}
private void startTracingImpl(Page page, StartTracingOptions options) {
if (options == null) {
options = new StartTracingOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (page != null) {
JsonObject jsonPage = new JsonObject();
jsonPage.addProperty("guid", ((PageImpl) page).guid);
params.add("page", jsonPage);
}
sendMessage("startTracing", params);
}
@Override
public byte[] stopTracing() {
return withLogging("Browser.stopTracing", () -> stopTracingImpl());
}
private byte[] stopTracingImpl() {
JsonObject json = sendMessage("stopTracing").getAsJsonObject();
return Base64.getDecoder().decode(json.get("binary").getAsString());
}
private Page newPageImpl(NewPageOptions options) {
BrowserContextImpl context = newContext(convertType(options, NewContextOptions.class));
BrowserContextImpl context = newContext(convertViaJson(options, NewContextOptions.class));
PageImpl page = context.newPage();
page.ownedContext = context;
context.ownerPage = page;
@@ -234,12 +136,8 @@ class BrowserImpl extends ChannelOwner implements Browser {
@Override
void handleEvent(String event, JsonObject parameters) {
if ("close".equals(event)) {
didClose();
isConnected = false;
listeners.notify(EventType.DISCONNECTED, this);
}
}
private void didClose() {
isConnected = false;
listeners.notify(EventType.DISCONNECTED, this);
}
}
@@ -16,22 +16,17 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.PlaywrightException;
import java.io.IOException;
import java.nio.file.Path;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Serialization.toProtocol;
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
LocalUtils localUtils;
BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@@ -47,80 +42,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonElement result = sendMessage("launch", params);
BrowserImpl browser = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
browser.localUtils = localUtils;
return browser;
}
@Override
public Browser connect(String wsEndpoint, ConnectOptions options) {
return withLogging("BrowserType.connect", () -> connectImpl(wsEndpoint, options));
}
private Browser connectImpl(String wsEndpoint, ConnectOptions options) {
if (options == null) {
options = new ConnectOptions();
}
// 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);
JsonObject json = sendMessage("connect", params).getAsJsonObject();
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
Connection connection = new Connection(pipe);
PlaywrightImpl playwright = connection.initializePlaywright();
if (!playwright.initializer.has("preLaunchedBrowser")) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace(System.err);
}
throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
}
playwright.initSharedSelectors(this.connection.getExistingObject("Playwright"));
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
browser.isRemote = true;
browser.isConnectedOverWebSocket = true;
browser.localUtils = localUtils;
Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
pipe.onClose(connectionCloseListener);
browser.onDisconnected(b -> {
playwright.unregisterSelectors();
pipe.offClose(connectionCloseListener);
try {
connection.close();
} catch (IOException e) {
e.printStackTrace(System.err);
}
});
return browser;
}
@Override
public Browser connectOverCDP(String endpointURL, ConnectOverCDPOptions options) {
if (!"chromium".equals(name())) {
throw new PlaywrightException("Connecting over CDP is only supported in Chromium.");
}
return withLogging("BrowserType.connectOverCDP", () -> connectOverCDPImpl(endpointURL, options));
}
private Browser connectOverCDPImpl(String endpointURL, ConnectOverCDPOptions options) {
if (options == null) {
options = new ConnectOverCDPOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("endpointURL", endpointURL);
JsonObject json = sendMessage("connectOverCDP", params).getAsJsonObject();
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.isRemote = true;
browser.localUtils = localUtils;
if (json.has("defaultContext")) {
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
browser.contexts.add(defaultContext);
}
return browser;
return connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
}
public String executablePath() {
@@ -139,48 +61,11 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("userDataDir", userDataDir.toString());
if (options.recordHarPath != null) {
JsonObject recordHar = new JsonObject();
recordHar.addProperty("path", options.recordHarPath.toString());
if (options.recordHarOmitContent != null) {
recordHar.addProperty("omitContent", true);
}
params.remove("recordHarPath");
params.remove("recordHarOmitContent");
params.add("recordHar", recordHar);
} else if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toString());
if (options.recordVideoSize != null) {
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
}
params.remove("recordVideoDir");
params.remove("recordVideoSize");
params.add("recordVideo", recordVideo);
} else if (options.recordVideoSize != null) {
throw new PlaywrightException("recordVideoSize is set but recordVideoDir is null");
}
if (options.viewportSize != null) {
if (options.viewportSize.isPresent()) {
JsonElement size = params.get("viewportSize");
params.remove("viewportSize");
params.add("viewport", size);
} else {
params.remove("viewportSize");
params.addProperty("noDefaultViewport", true);
}
}
JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject();
BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
context.videosDir = options.recordVideoDir;
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
if (options.recordVideo != null) {
context.videosDir = options.recordVideo.dir;
}
context.recordHarPath = options.recordHarPath;
context.tracing().localUtils = localUtils;
return context;
}
@@ -22,7 +22,6 @@ import com.google.gson.JsonObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
class ChannelOwner extends LoggingSupport {
final Connection connection;
@@ -68,20 +67,6 @@ class ChannelOwner extends LoggingSupport {
objects.clear();
}
<T> T withWaitLogging(String apiName, Supplier<T> code) {
return new WaitForEventLogger<>(this, apiName, code).get();
}
@Override
<T> T withLogging(String apiName, Supplier<T> code) {
String previousApiName = connection.setApiName(apiName);
try {
return super.withLogging(apiName, code);
} finally {
connection.setApiName(previousApiName);
}
}
WaitableResult<JsonElement> sendMessageAsync(String method, JsonObject params) {
return connection.sendMessageAsync(guid, method, params);
}
@@ -18,14 +18,11 @@ package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.TimeoutError;
import java.io.IOException;
import java.io.*;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import static com.microsoft.playwright.impl.Serialization.gson;
@@ -56,45 +53,17 @@ public class Connection {
private final Map<String, ChannelOwner> objects = new HashMap<>();
private final Root root;
private int lastId = 0;
private final StackTraceCollector stackTraceCollector;
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
private String apiName;
private static final boolean isLogging;
static {
String debug = System.getenv("DEBUG");
isLogging = (debug != null) && debug.contains("pw:channel");
}
class Root extends ChannelOwner {
Root(Connection connection) {
super(connection, "Root", "");
}
Playwright initialize() {
JsonObject params = new JsonObject();
params.addProperty("sdkLanguage", "java");
JsonElement result = sendMessage("initialize", params.getAsJsonObject());
return this.connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("playwright").get("guid").getAsString());
super(connection, "", "");
}
}
Connection(Transport transport) {
if (isLogging) {
transport = new TransportLogger(transport);
}
this.transport = transport;
public Connection(InputStream in, OutputStream out) {
transport = new Transport(in, out);
root = new Root(this);
stackTraceCollector = StackTraceCollector.createFromEnv();
}
boolean isCollectingStacks() {
return stackTraceCollector != null;
}
String setApiName(String name) {
String previous = apiName;
apiName = name;
return previous;
}
void close() throws IOException {
@@ -118,24 +87,15 @@ public class Connection {
message.addProperty("guid", guid);
message.addProperty("method", method);
message.add("params", params);
JsonObject metadata = new JsonObject();
if (apiName == null) {
metadata.addProperty("internal", true);
} else {
metadata.addProperty("apiName", apiName);
// All but first message in an API call are considered internal and will be hidden from the inspector.
apiName = null;
if (stackTraceCollector != null) {
metadata.add("stack", stackTraceCollector.currentStackTrace());
}
}
message.add("metadata", metadata);
transport.send(message);
transport.send(gson().toJson(message));
return result;
}
public PlaywrightImpl initializePlaywright() {
return (PlaywrightImpl) this.root.initialize();
public ChannelOwner waitForObjectWithKnownName(String guid) {
while (!objects.containsKey(guid)) {
processOneMessage();
}
return objects.get(guid);
}
public <T> T getExistingObject(String guid) {
@@ -154,13 +114,13 @@ public class Connection {
}
void processOneMessage() {
JsonObject message = transport.poll(Duration.ofMillis(10));
if (message == null) {
String messageString = transport.poll(Duration.ofMillis(10));
if (messageString == null) {
return;
}
Gson gson = gson();
Message messageObj = gson.fromJson(message, Message.class);
dispatch(messageObj);
Message message = gson.fromJson(messageString, Message.class);
dispatch(message);
}
private void dispatch(Message message) {
@@ -175,12 +135,10 @@ public class Connection {
if (message.error == null) {
callback.complete(message.result);
} else {
if (message.error.error == null) {
callback.completeExceptionally(new PlaywrightException(message.error.toString()));
} else if ("TimeoutError".equals(message.error.error.name)) {
callback.completeExceptionally(new TimeoutError(message.error.error.toString()));
if (message.error.error != null) {
callback.completeExceptionally(new ServerException(message.error.error));
} else {
callback.completeExceptionally(new DriverException(message.error.error));
callback.completeExceptionally(new PlaywrightException(message.error.toString()));
}
}
return;
@@ -229,9 +187,6 @@ public class Connection {
case "AndroidDevice":
// result = new AndroidDevice(parent, type, guid, initializer);
break;
case "Artifact":
result = new ArtifactImpl(parent, type, guid, initializer);
break;
case "BindingCall":
result = new BindingCall(parent, type, guid, initializer);
break;
@@ -250,28 +205,21 @@ public class Connection {
case "Dialog":
result = new DialogImpl(parent, type, guid, initializer);
break;
case "Download":
result = new DownloadImpl(parent, type, guid, initializer);
break;
case "Electron":
// result = new Playwright(parent, type, guid, initializer);
break;
case "ElementHandle":
result = new ElementHandleImpl(parent, type, guid, initializer);
break;
case "APIRequestContext":
// Create fake object as this API is experimental an only exposed in Node.js.
result = new APIRequestContextImpl(parent, type, guid, initializer);
break;
case "Frame":
result = new FrameImpl(parent, type, guid, initializer);
break;
case "JSHandle":
result = new JSHandleImpl(parent, type, guid, initializer);
break;
case "JsonPipe":
result = new JsonPipe(parent, type, guid, initializer);
break;
case "LocalUtils":
result = new LocalUtils(parent, type, guid, initializer);
break;
case "Page":
result = new PageImpl(parent, type, guid, initializer);
break;
@@ -293,9 +241,6 @@ public class Connection {
case "Selectors":
result = new SelectorsImpl(parent, type, guid, initializer);
break;
case "Tracing":
result = new TracingImpl(parent, type, guid, initializer);
break;
case "WebSocket":
result = new WebSocketImpl(parent, type, guid, initializer);
break;
@@ -48,11 +48,7 @@ public class ConsoleMessageImpl extends ChannelOwner implements ConsoleMessage {
return result;
}
@Override
public String location() {
JsonObject location = initializer.getAsJsonObject("location");
return location.get("url").getAsString() + ":" +
location.get("lineNumber").getAsNumber() + ":" +
location.get("columnNumber").getAsNumber();
public Location location() {
return gson().fromJson(initializer.get("location"), Location.class);
}
}
@@ -0,0 +1,80 @@
/*
* 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 com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.DeviceDescriptor;
import com.microsoft.playwright.PlaywrightException;
class DeviceDescriptorImpl implements DeviceDescriptor {
PlaywrightImpl playwright;
private static class ViewportImpl implements Viewport{
private int width;
private int height;
@Override
public int width() {
return width;
}
@Override
public int height() {
return height;
}
}
private ViewportImpl viewport;
private String userAgent;
private double deviceScaleFactor;
private boolean isMobile;
private boolean hasTouch;
private String defaultBrowserType;
@Override
public Viewport viewport() {
return viewport;
}
@Override
public String userAgent() {
return userAgent;
}
@Override
public double deviceScaleFactor() {
return deviceScaleFactor;
}
@Override
public boolean isMobile() {
return isMobile;
}
@Override
public boolean hasTouch() {
return hasTouch;
}
@Override
public BrowserType defaultBrowserType() {
switch (defaultBrowserType) {
case "chromium": return playwright.chromium();
case "firefox": return playwright.firefox();
case "webkit": return playwright.webkit();
default: throw new PlaywrightException("Unknown type: " + defaultBrowserType);
}
}
}
@@ -18,6 +18,7 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Dialog;
import com.microsoft.playwright.PlaywrightException;
class DialogImpl extends ChannelOwner implements Dialog {
DialogImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@@ -51,7 +52,13 @@ class DialogImpl extends ChannelOwner implements Dialog {
}
@Override
public String type() {
return initializer.get("type").getAsString();
public Type type() {
switch (initializer.get("type").getAsString()) {
case "alert": return Type.ALERT;
case "beforeunload": return Type.BEFOREUNLOAD;
case "confirm": return Type.CONFIRM;
case "prompt": return Type.PROMPT;
default: throw new PlaywrightException("Unexpected dialog type: " + initializer.get("type").getAsString());
}
}
}
@@ -18,20 +18,14 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Download;
import com.microsoft.playwright.Page;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.Path;
class DownloadImpl implements Download {
private final PageImpl page;
private final ArtifactImpl artifact;
private final JsonObject initializer;
DownloadImpl(PageImpl page, ArtifactImpl artifact, JsonObject initializer) {
this.page = page;
this.artifact = artifact;
this.initializer = initializer;
public class DownloadImpl extends ChannelOwner implements Download {
public DownloadImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
@@ -44,38 +38,50 @@ class DownloadImpl implements Download {
return initializer.get("suggestedFilename").getAsString();
}
@Override
public void cancel() {
page.withLogging("Download.cancel", () -> artifact.cancel());
}
@Override
public InputStream createReadStream() {
return page.withLogging("Download.createReadStream", () -> artifact.createReadStream());
return withLogging("Download.createReadStream", () -> {
JsonObject result = sendMessage("stream").getAsJsonObject();
if (!result.has("stream")) {
return null;
}
Stream stream = connection.getExistingObject(result.getAsJsonObject("stream").get("guid").getAsString());
return stream.stream();
});
}
@Override
public void delete() {
page.withLogging("Download.delete", () -> artifact.delete());
withLogging("Download.delete", () -> {
sendMessage("delete");
});
}
@Override
public String failure() {
return page.withLogging("Download.failure", () -> artifact.failure());
}
@Override
public Page page() {
return page;
return withLogging("Download.failure", () -> {
JsonObject result = sendMessage("failure").getAsJsonObject();
if (result.has("error")) {
return result.get("error").getAsString();
}
return null;
});
}
@Override
public Path path() {
return page.withLogging("Download.path", () -> artifact.pathAfterFinished());
return withLogging("Download.path", () -> {
JsonObject json = sendMessage("path").getAsJsonObject();
return FileSystems.getDefault().getPath(json.get("value").getAsString());
});
}
@Override
public void saveAs(Path path) {
page.withLogging("Download.saveAs", () -> artifact.saveAs(path));
withLogging("Download.saveAs", () -> {
JsonObject params = new JsonObject();
params.addProperty("path", path.toString());
sendMessage("saveAs", params);
});
}
}
@@ -20,22 +20,16 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.FileChooser;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.options.BoundingBox;
import com.microsoft.playwright.options.ElementState;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.SelectOption;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
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.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@@ -85,6 +79,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelector", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
@@ -98,6 +93,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelectorAll", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
@@ -139,6 +135,16 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
options = new ClickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("button");
if (options.button != null) {
params.addProperty("button", Serialization.toProtocol(options.button));
}
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("click", params);
}
@@ -165,6 +171,16 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
options = new DblclickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("button");
if (options.button != null) {
params.addProperty("button", Serialization.toProtocol(options.button));
}
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("dblclick", params);
}
@@ -209,15 +225,14 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void hover(HoverOptions options) {
withLogging("ElementHandle.hover", () -> hoverImpl(options));
}
private void hoverImpl(HoverOptions options) {
if (options == null) {
options = new HoverOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("hover", params);
withLogging("ElementHandle.hover", () -> {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("hover", params);
});
}
@Override
@@ -236,20 +251,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
});
}
@Override
public String inputValue(InputValueOptions options) {
return withLogging("ElementHandle.inputValue", () -> inputValueImpl(options));
}
private String inputValueImpl(InputValueOptions options) {
if (options == null) {
options = new InputValueOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject json = sendMessage("inputValue", params).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public boolean isChecked() {
return withLogging("ElementHandle.isChecked", () -> {
@@ -322,6 +323,10 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
sendMessage("press", params);
}
private static String toProtocol(ScreenshotOptions.Type type) {
return type.toString().toLowerCase();
}
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withLogging("ElementHandle.screenshot", () -> screenshotImpl(options));
@@ -332,19 +337,21 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
options = new ScreenshotOptions();
}
if (options.type == null) {
options.type = PNG;
options.type = ScreenshotOptions.Type.PNG;
if (options.path != null) {
String fileName = options.path.getFileName().toString();
int extStart = fileName.lastIndexOf('.');
if (extStart != -1) {
String extension = fileName.substring(extStart).toLowerCase();
if (".jpeg".equals(extension) || ".jpg".equals(extension)) {
options.type = JPEG;
options.type = ScreenshotOptions.Type.JPEG;
}
}
}
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("type");
params.addProperty("type", toProtocol(options.type));
params.remove("path");
JsonObject json = sendMessage("screenshot", params).getAsJsonObject();
@@ -360,33 +367,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
withLogging("ElementHandle.scrollIntoViewIfNeeded", () -> scrollIntoViewIfNeededImpl(options));
}
@Override
public List<String> selectOption(String value, SelectOptionOptions options) {
String[] values = value == null ? null : new String[]{ value };
return selectOption(values, options);
}
@Override
public List<String> selectOption(ElementHandle value, SelectOptionOptions options) {
ElementHandle[] values = value == null ? null : new ElementHandle[]{ value };
return selectOption(values, options);
}
@Override
public List<String> selectOption(String[] values, SelectOptionOptions options) {
if (values == null) {
return selectOption(new SelectOption[0], options);
}
return selectOption(Arrays.asList(values).stream().map(
v -> new SelectOption().setValue(v)).toArray(SelectOption[]::new), options);
}
@Override
public List<String> selectOption(SelectOption value, SelectOptionOptions options) {
SelectOption[] values = value == null ? null : new SelectOption[]{ value };
return selectOption(values, options);
}
private void scrollIntoViewIfNeededImpl(ScrollIntoViewIfNeededOptions options) {
if (options == null) {
options = new ScrollIntoViewIfNeededOptions();
@@ -420,7 +400,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
private List<String> selectOption(JsonObject params) {
return withLogging("SelectOption", () -> {
return withLogging("ElementHandle.selectOption", () -> {
JsonObject json = sendMessage("selectOption", params).getAsJsonObject();
return parseStringList(json.getAsJsonArray("values"));
});
@@ -431,20 +411,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
withLogging("ElementHandle.selectText", () -> selectTextImpl(options));
}
@Override
public void setChecked(boolean checked, SetCheckedOptions options) {
if (checked) {
check(convertType(options, CheckOptions.class));
} else {
uncheck(convertType(options, UncheckOptions.class));
}
}
@Override
public void setInputFiles(Path files, SetInputFilesOptions options) {
setInputFiles(new Path[]{files}, options);
}
private void selectTextImpl(SelectTextOptions options) {
if (options == null) {
options = new SelectTextOptions();
@@ -459,16 +425,11 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
@Override
public void setInputFiles(FilePayload files, SetInputFilesOptions options) {
setInputFiles(new FilePayload[]{files}, options);
}
@Override
public void setInputFiles(FilePayload[] files, SetInputFilesOptions options) {
public void setInputFiles(FileChooser.FilePayload[] files, SetInputFilesOptions options) {
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
}
void setInputFilesImpl(FilePayload[] files, SetInputFilesOptions options) {
void setInputFilesImpl(FileChooser.FilePayload[] files, SetInputFilesOptions options) {
if (options == null) {
options = new SetInputFilesOptions();
}
@@ -487,6 +448,10 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
options = new TapOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (options.modifiers != null) {
params.remove("modifiers");
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("tap", params);
}
@@ -538,15 +503,15 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
if (options == null) {
options = new WaitForElementStateOptions();
}
if (state == null) {
throw new IllegalArgumentException("State cannot be null");
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("state", toProtocol(state));
sendMessage("waitForElementState", params);
}
private static String toProtocol(ElementState state) {
if (state == null) {
throw new IllegalArgumentException("State cannot by null");
}
return state.toString().toLowerCase();
}
@@ -560,6 +525,8 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
options = new WaitForSelectorOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("state");
params.addProperty("state", toProtocol(options.state));
params.addProperty("selector", selector);
JsonElement json = sendMessage("waitForSelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
@@ -568,4 +535,11 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
return connection.getExistingObject(element.get("guid").getAsString());
}
private static String toProtocol(WaitForSelectorOptions.State state) {
if (state == null) {
state = WaitForSelectorOptions.State.VISIBLE;
}
return state.toString().toLowerCase();
}
}
@@ -19,11 +19,10 @@ package com.microsoft.playwright.impl;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.FileChooser;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.FilePayload;
import java.nio.file.Path;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
class FileChooserImpl implements FileChooser {
private final PageImpl page;
@@ -51,24 +50,14 @@ class FileChooserImpl implements FileChooser {
return page;
}
@Override
public void setFiles(Path files, SetFilesOptions options) {
setFiles(new Path[]{files}, options);
}
@Override
public void setFiles(Path[] files, SetFilesOptions options) {
setFiles(Utils.toFilePayloads(files), options);
}
@Override
public void setFiles(FilePayload files, SetFilesOptions options) {
setFiles(new FilePayload[]{files}, options);
}
@Override
public void setFiles(FilePayload[] files, SetFilesOptions options) {
page.withLogging("FileChooser.setInputFiles",
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
() -> element.setInputFilesImpl(files, convertViaJson(options, ElementHandle.SetInputFilesOptions.class)));
}
}
@@ -1,58 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.FormData;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
public class FormDataImpl implements FormData {
Map<String, Object> fields = new LinkedHashMap<>();
@Override
public FormData set(String name, String value) {
fields.put(name, value);
return this;
}
@Override
public FormData set(String name, boolean value) {
fields.put(name, value);
return this;
}
@Override
public FormData set(String name, int value) {
fields.put(name, value);
return this;
}
@Override
public FormData set(String name, Path value) {
fields.put(name, value);
return this;
}
@Override
public FormData set(String name, FilePayload value) {
fields.put(name, value);
return this;
}
}
@@ -20,7 +20,6 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -28,20 +27,18 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
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.options.WaitUntilState.*;
import static com.microsoft.playwright.Frame.LoadState.*;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
public class FrameImpl extends ChannelOwner implements Frame {
private String name;
private String url;
FrameImpl parentFrame;
Set<FrameImpl> childFrames = new LinkedHashSet<>();
private final Set<WaitUntilState> loadStates = new HashSet<>();
private final Set<LoadState> loadStates = new HashSet<>();
enum InternalEventType { NAVIGATED, LOADSTATE }
private final ListenerCollection<InternalEventType> internalListeners = new ListenerCollection<>();
PageImpl page;
@@ -61,26 +58,22 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
}
private static WaitUntilState loadStateFromProtocol(String value) {
private static LoadState loadStateFromProtocol(String value) {
switch (value) {
case "load": return LOAD;
case "domcontentloaded": return DOMCONTENTLOADED;
case "networkidle": return NETWORKIDLE;
case "commit": return COMMIT;
default: throw new PlaywrightException("Unexpected value: " + value);
}
}
@Override
public ElementHandle querySelector(String selector, QuerySelectorOptions options) {
return withLogging("Frame.querySelector", () -> querySelectorImpl(selector, options));
public ElementHandle querySelector(String selector) {
return withLogging("Frame.querySelector", () -> querySelectorImpl(selector));
}
ElementHandleImpl querySelectorImpl(String selector, QuerySelectorOptions options) {
if (options == null) {
options = new QuerySelectorOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
ElementHandleImpl querySelectorImpl(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
@@ -95,33 +88,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
return withLogging("Frame.querySelectorAll", () -> querySelectorAllImpl(selector));
}
@Override
public List<String> selectOption(String selector, String value, SelectOptionOptions options) {
String[] values = value == null ? null : new String[]{ value };
return selectOption(selector, values, options);
}
@Override
public List<String> selectOption(String selector, ElementHandle value, SelectOptionOptions options) {
ElementHandle[] values = value == null ? null : new ElementHandle[]{value};
return selectOption(selector, values, options);
}
@Override
public List<String> selectOption(String selector, String[] values, SelectOptionOptions options) {
if (values == null) {
return selectOption(selector, new SelectOption[0], options);
}
return selectOption(selector, Arrays.asList(values).stream().map(
v -> new SelectOption().setValue(v)).toArray(SelectOption[]::new), options);
}
@Override
public List<String> selectOption(String selector, SelectOption value, SelectOptionOptions options) {
SelectOption[] values = value == null ? null : new SelectOption[]{value};
return selectOption(selector, values, options);
}
List<ElementHandle> querySelectorAllImpl(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
@@ -138,17 +104,15 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return withLogging("Frame.evalOnSelector", () -> evalOnSelectorImpl(selector, pageFunction, arg, options));
public Object evalOnSelector(String selector, String pageFunction, Object arg) {
return withLogging("Frame.evalOnSelector", () -> evalOnSelectorImpl(selector, pageFunction, arg));
}
Object evalOnSelectorImpl(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
if (options == null) {
options = new EvalOnSelectorOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
Object evalOnSelectorImpl(String selector, String pageFunction, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelector", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
@@ -164,6 +128,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelectorAll", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
@@ -252,6 +217,17 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.remove("button");
if (options.button != null) {
params.addProperty("button", Serialization.toProtocol(options.button));
}
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("click", params);
}
@@ -275,6 +251,17 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.remove("button");
if (options.button != null) {
params.addProperty("button", Serialization.toProtocol(options.button));
}
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("dblclick", params);
}
@@ -303,6 +290,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = new JsonObject();
params.addProperty("expression", expression);
params.addProperty("world", "main");
params.addProperty("isFunction", isFunctionBody(expression));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
@@ -318,6 +306,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
@@ -358,11 +347,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public FrameLocator frameLocator(String selector) {
return new FrameLocatorImpl(this, selector);
}
ElementHandle frameElementImpl() {
JsonObject json = sendMessage("frameElement").getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("element").get("guid").getAsString());
@@ -398,6 +382,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("url", url);
if (options.waitUntil != null) {
params.remove("waitUntil");
params.addProperty("waitUntil", toProtocol(options.waitUntil));
}
JsonElement result = sendMessage("goto", params);
JsonObject jsonResponse = result.getAsJsonObject().getAsJsonObject("response");
if (jsonResponse == null) {
@@ -420,21 +408,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
sendMessage("hover", params);
}
@Override
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
withLogging("Frame.dragAndDrop", () -> dragAndDropImpl(source, target, options));
}
void dragAndDropImpl(String source, String target, DragAndDropOptions options) {
if (options == null) {
options = new DragAndDropOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("source", source);
params.addProperty("target", target);
sendMessage("dragAndDrop", params);
}
@Override
public String innerHTML(String selector, InnerHTMLOptions options) {
return withLogging("Frame.innerHTML", () -> innerHTMLImpl(selector, options));
@@ -465,21 +438,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
return json.get("value").getAsString();
}
@Override
public String inputValue(String selector, InputValueOptions options) {
return withLogging("Frame.inputValue", () -> inputValueImpl(selector, options));
}
String inputValueImpl(String selector, InputValueOptions options) {
if (options == null) {
options = new InputValueOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("inputValue", params).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public boolean isChecked(String selector, IsCheckedOptions options) {
return withLogging("Page.isChecked", () -> isCheckedImpl(selector, options));
@@ -565,11 +523,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
return withLogging("Page.isVisible", () -> isVisibleImpl(selector, options));
}
@Override
public Locator locator(String selector, LocatorOptions options) {
return new LocatorImpl(this, selector, convertType(options, Locator.LocatorOptions.class));
}
boolean isVisibleImpl(String selector, IsVisibleOptions options) {
if (options == null) {
options = new IsVisibleOptions();
@@ -586,7 +539,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public PageImpl page() {
public Page page() {
return page;
}
@@ -611,11 +564,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) {
public List<String> selectOption(String selector, ElementHandle.SelectOption[] values, SelectOptionOptions options) {
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options));
}
List<String> selectOptionImpl(String selector, SelectOption[] values, SelectOptionOptions options) {
List<String> selectOptionImpl(String selector, ElementHandle.SelectOption[] values, SelectOptionOptions options) {
if (options == null) {
options = new SelectOptionOptions();
}
@@ -649,16 +602,15 @@ public class FrameImpl extends ChannelOwner implements Frame {
return parseStringList(json.getAsJsonArray("values"));
}
@Override
public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
withLogging("Frame.setChecked", () -> setCheckedImpl(selector, checked, options));
}
void setCheckedImpl(String selector, boolean checked, SetCheckedOptions options) {
if (checked) {
checkImpl(selector, convertType(options, CheckOptions.class));
} else {
uncheckImpl(selector, convertType(options, UncheckOptions.class));
static String toProtocol(LoadState waitUntil) {
if (waitUntil == null) {
waitUntil = LoadState.LOAD;
}
switch (waitUntil) {
case DOMCONTENTLOADED: return "domcontentloaded";
case LOAD: return "load";
case NETWORKIDLE: return "networkidle";
default: throw new PlaywrightException("Unexpected value: " + waitUntil);
}
}
@@ -667,17 +619,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
withLogging("Frame.setContent", () -> setContentImpl(html, options));
}
@Override
public void setInputFiles(String selector, Path files, SetInputFilesOptions options) {
setInputFiles(selector, new Path[] {files}, options);
}
void setContentImpl(String html, SetContentOptions options) {
if (options == null) {
options = new SetContentOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("html", html);
params.remove("waitUntil");
params.addProperty("waitUntil", toProtocol(options.waitUntil));
sendMessage("setContent", params);
}
@@ -686,21 +635,16 @@ public class FrameImpl extends ChannelOwner implements Frame {
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, 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) {
public void setInputFiles(String selector, FileChooser.FilePayload[] files, SetInputFilesOptions options) {
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
}
void setInputFilesImpl(String selector, FilePayload[] files, SetInputFilesOptions options) {
void setInputFilesImpl(String selector, FileChooser.FilePayload[] files, SetInputFilesOptions options) {
if (options == null) {
options = new SetInputFilesOptions();
}
@@ -719,6 +663,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
options = new TapOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
params.addProperty("selector", selector);
sendMessage("tap", params);
}
@@ -792,6 +740,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("waitForFunction", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("handle");
@@ -800,17 +749,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void waitForLoadState(LoadState state, WaitForLoadStateOptions options) {
withWaitLogging("Frame.waitForLoadState", () -> {
waitForLoadStateImpl(state, options);
return null;
});
withLogging("Frame.waitForLoadState", () -> waitForLoadStateImpl(state, options));
}
void waitForLoadStateImpl(LoadState state, WaitForLoadStateOptions options) {
waitForLoadStateImpl(convertType(state, WaitUntilState.class), options);
}
private void waitForLoadStateImpl(WaitUntilState state, WaitForLoadStateOptions options) {
if (options == null) {
options = new WaitForLoadStateOptions();
}
@@ -825,11 +767,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
runUntil(() -> {}, new WaitableRace<>(waitables));
}
private class WaitForLoadStateHelper implements Waitable<Void>, Consumer<WaitUntilState> {
private final WaitUntilState expectedState;
private class WaitForLoadStateHelper implements Waitable<Void>, Consumer<LoadState> {
private final LoadState expectedState;
private boolean isDone;
WaitForLoadStateHelper(WaitUntilState state) {
WaitForLoadStateHelper(LoadState state) {
expectedState = state;
isDone = loadStates.contains(state);
if (!isDone) {
@@ -838,7 +780,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public void accept(WaitUntilState state) {
public void accept(LoadState state) {
if (expectedState.equals(state)) {
isDone = true;
dispose();
@@ -861,13 +803,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
private class WaitForNavigationHelper implements Waitable<Response>, Consumer<JsonObject> {
private final UrlMatcher matcher;
private final WaitUntilState expectedLoadState;
private final LoadState expectedLoadState;
private WaitForLoadStateHelper loadStateHelper;
private RequestImpl request;
private RuntimeException exception;
WaitForNavigationHelper(UrlMatcher matcher, WaitUntilState expectedLoadState) {
WaitForNavigationHelper(UrlMatcher matcher, LoadState expectedLoadState) {
this.matcher = matcher;
this.expectedLoadState = expectedLoadState;
internalListeners.add(InternalEventType.NAVIGATED, this);
@@ -925,26 +867,20 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public Response waitForNavigation(WaitForNavigationOptions options, Runnable code) {
return withLogging("Frame.waitForNavigation", () -> waitForNavigationImpl(code, options, null));
public Response waitForNavigation(Runnable code, WaitForNavigationOptions options) {
return withLogging("Frame.waitForNavigation", () -> waitForNavigationImpl(code, options));
}
Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options) {
return waitForNavigationImpl(code, options, null);
}
private Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options, UrlMatcher matcher) {
if (options == null) {
options = new WaitForNavigationOptions();
}
if (options.waitUntil == null) {
options.waitUntil = WaitUntilState.LOAD;
options.waitUntil = LOAD;
}
List<Waitable<Response>> waitables = new ArrayList<>();
if (matcher == null) {
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url);
}
UrlMatcher matcher = UrlMatcher.forOneOf(options.glob, options.pattern, options.predicate);
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil));
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableFrameDetach(this));
@@ -952,22 +888,25 @@ public class FrameImpl extends ChannelOwner implements Frame {
return runUntil(code, new WaitableRace<>(waitables));
}
private static String toProtocol(WaitForSelectorOptions.State state) {
return state.toString().toLowerCase();
}
@Override
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
return withLogging("Frame.waitForSelector", () -> waitForSelectorImpl(selector, options));
}
ElementHandle waitForSelectorImpl(String selector, WaitForSelectorOptions options) {
return waitForSelectorImpl(selector, options, false);
}
ElementHandle waitForSelectorImpl(String selector, WaitForSelectorOptions options, boolean omitReturnValue) {
if (options == null) {
options = new WaitForSelectorOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("omitReturnValue", omitReturnValue);
if (options.state != null) {
params.remove("state");
params.addProperty("state", toProtocol(options.state));
}
JsonElement json = sendMessage("waitForSelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
@@ -982,53 +921,20 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
void waitForTimeoutImpl(double timeout) {
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("waitForTimeout", params);
}
@Override
public void waitForURL(String url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(page.context().baseUrl, url), options);
}
@Override
public void waitForURL(Pattern url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(url), options);
}
@Override
public void waitForURL(Predicate<String> url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(url), options);
}
private void waitForURL(UrlMatcher matcher, WaitForURLOptions options) {
withLogging("Frame.waitForURL", () -> waitForURLImpl(matcher, options));
}
void waitForURLImpl(UrlMatcher matcher, WaitForURLOptions options) {
if (options == null) {
options = new WaitForURLOptions();
}
if (matcher.test(url())) {
waitForLoadStateImpl(options.waitUntil, convertType(options, WaitForLoadStateOptions.class));
return;
}
waitForNavigationImpl(() -> {}, convertType(options, WaitForNavigationOptions.class), matcher);
}
int queryCount(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonObject result = sendMessage("queryCount", params).getAsJsonObject();
return result.get("value").getAsInt();
runUntil(() -> {}, new WaitableTimeout<Void>(timeout) {
@Override
public Void get() {
// Override to not throw.
return null;
}
});
}
protected void handleEvent(String event, JsonObject params) {
if ("loadstate".equals(event)) {
JsonElement add = params.get("add");
if (add != null) {
WaitUntilState state = loadStateFromProtocol(add.getAsString());
LoadState state = loadStateFromProtocol(add.getAsString());
loadStates.add(state);
internalListeners.notify(InternalEventType.LOADSTATE, state);
}
@@ -1,57 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.FrameLocator;
import com.microsoft.playwright.Locator;
import static com.microsoft.playwright.impl.Utils.convertType;
class FrameLocatorImpl implements FrameLocator {
private final FrameImpl frame;
private final String frameSelector;
FrameLocatorImpl(FrameImpl frame, String selector) {
this.frame = frame;
this.frameSelector = selector;
}
@Override
public FrameLocator first() {
return new FrameLocatorImpl(frame, frameSelector + " >> nth=0");
}
@Override
public FrameLocatorImpl frameLocator(String selector) {
return new FrameLocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector);
}
@Override
public FrameLocator last() {
return new FrameLocatorImpl(frame, frameSelector + " >> nth=-1");
}
@Override
public Locator locator(String selector, LocatorOptions options) {
return new LocatorImpl(frame, frameSelector + " >> control=enter-frame >> " + selector, convertType(options, Locator.LocatorOptions.class));
}
@Override
public FrameLocator nth(int index) {
return new FrameLocatorImpl(frame, frameSelector + " >> nth=" + index);
}
}
@@ -25,6 +25,7 @@ import java.util.HashMap;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
public class JSHandleImpl extends ChannelOwner implements JSHandle {
private String preview;
@@ -50,6 +51,7 @@ public class JSHandleImpl extends ChannelOwner implements JSHandle {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
@@ -63,6 +65,7 @@ public class JSHandleImpl extends ChannelOwner implements JSHandle {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
@@ -1,109 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.LinkedList;
import java.util.Queue;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.gson;
class JsonPipe extends ChannelOwner implements Transport {
private final Queue<JsonObject> incoming = new LinkedList<>();
private ListenerCollection<EventType> listeners = new ListenerCollection<>();
private enum EventType { CLOSE }
private boolean isClosed;
JsonPipe(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public void send(JsonObject message) {
checkIfClosed();
JsonObject params = new JsonObject();
params.add("message", message);
sendMessage("send", params);
}
@Override
public JsonObject poll(Duration timeout) {
Instant start = Instant.now();
return runUntil(() -> {}, new Waitable<JsonObject>() {
JsonObject message;
@Override
public boolean isDone() {
if (!incoming.isEmpty()) {
message = incoming.remove();
return true;
}
checkIfClosed();
if (Duration.between(start, Instant.now()).compareTo(timeout) > 0) {
return true;
}
return false;
}
@Override
public JsonObject get() {
return message;
}
@Override
public void dispose() {
}
});
}
@Override
public void close() throws IOException {
if (!isClosed) {
sendMessage("close");
}
}
void onClose(Consumer<JsonPipe> handler) {
listeners.add(EventType.CLOSE, handler);
}
void offClose(Consumer<JsonPipe> handler) {
listeners.remove(EventType.CLOSE, handler);
}
@Override
protected void handleEvent(String event, JsonObject params) {
if ("message".equals(event)) {
incoming.add(params.get("message").getAsJsonObject());
} else if ("closed".equals(event)) {
isClosed = true;
listeners.notify(EventType.CLOSE, this);
}
}
private void checkIfClosed() {
if (isClosed) {
throw new PlaywrightException("Browser has been closed");
}
}
}
@@ -21,7 +21,7 @@ import com.microsoft.playwright.Keyboard;
import static com.microsoft.playwright.impl.Serialization.gson;
class KeyboardImpl implements Keyboard {
class KeyboardImpl extends LoggingSupport implements Keyboard {
private final ChannelOwner page;
KeyboardImpl(ChannelOwner page) {
@@ -30,7 +30,7 @@ class KeyboardImpl implements Keyboard {
@Override
public void down(String key) {
page.withLogging("Keyboard.down", () -> {
withLogging("Keyboard.down", () -> {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardDown", params);
@@ -39,7 +39,7 @@ class KeyboardImpl implements Keyboard {
@Override
public void insertText(String text) {
page.withLogging("Keyboard.insertText", () -> {
withLogging("Keyboard.insertText", () -> {
JsonObject params = new JsonObject();
params.addProperty("text", text);
page.sendMessage("keyboardInsertText", params);
@@ -48,7 +48,7 @@ class KeyboardImpl implements Keyboard {
@Override
public void press(String key, PressOptions options) {
page.withLogging("Keyboard.press", () -> pressImpl(key, options));
withLogging("Keyboard.press", () -> pressImpl(key, options));
}
private void pressImpl(String key, PressOptions options) {
@@ -62,7 +62,7 @@ class KeyboardImpl implements Keyboard {
@Override
public void type(String text, TypeOptions options) {
page.withLogging("Keyboard.type", () -> typeImpl(text, options));
withLogging("Keyboard.type", () -> typeImpl(text, options));
}
private void typeImpl(String text, TypeOptions options) {
@@ -76,7 +76,7 @@ class KeyboardImpl implements Keyboard {
@Override
public void up(String key) {
page.withLogging("Keyboard.up", () -> {
withLogging("Keyboard.up", () -> {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardUp", params);
@@ -1,35 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.nio.file.Path;
class LocalUtils extends ChannelOwner {
LocalUtils(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
void zip(Path zipFile, JsonArray entries) {
JsonObject params = new JsonObject();
params.addProperty("zipFile", zipFile.toString());
params.add("entries", entries);
sendMessage("zip", params);
}
}
@@ -1,308 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.assertions.LocatorAssertions;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.serializeArgument;
import static com.microsoft.playwright.impl.Utils.convertType;
public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAssertions {
public LocatorAssertionsImpl(Locator locator) {
this(locator, false);
}
private LocatorAssertionsImpl(Locator locator, boolean isNot) {
super((LocatorImpl) locator, isNot);
}
@Override
public void containsText(String text, ContainsTextOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
}
@Override
public void containsText(Pattern pattern, ContainsTextOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void containsText(String[] strings, ContainsTextOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (String text : strings) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
}
@Override
public void containsText(Pattern[] patterns, ContainsTextOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (Pattern pattern : patterns) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasAttribute(String name, String text, HasAttributeOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
hasAttribute(name, expected, text, options);
}
@Override
public void hasAttribute(String name, Pattern pattern, HasAttributeOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
hasAttribute(name, expected, pattern, options);
}
private void hasAttribute(String name, ExpectedTextValue expectedText, Object expectedValue, HasAttributeOptions options) {
if (options == null) {
options = new HasAttributeOptions();
}
FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
commonOptions.expressionArg = name;
String message = "Locator expected to have attribute '" + name + "'";
if (expectedValue instanceof Pattern) {
message += " matching regex";
}
expectImpl("to.have.attribute", expectedText, expectedValue, message, commonOptions);
}
@Override
public void hasClass(String text, HasClassOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expectImpl("to.have.class", expected, text, "Locator expected to have class", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasClass(Pattern pattern, HasClassOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.class", expected, pattern, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasClass(String[] strings, HasClassOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (String text : strings) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
list.add(expected);
}
expectImpl("to.have.class.array", list, strings, "Locator expected to have class", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasClass(Pattern[] patterns, HasClassOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (Pattern pattern : patterns) {
ExpectedTextValue expected = expectedRegex(pattern);
list.add(expected);
}
expectImpl("to.have.class.array", list, patterns, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasCount(int count, HasCountOptions options) {
if (options == null) {
options = new HasCountOptions();
}
FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
commonOptions.expectedNumber = count;
List<ExpectedTextValue> expectedText = null;
expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions);
}
@Override
public void hasCSS(String name, String value, HasCSSOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = value;
hasCSS(name, expected, value, options);
}
@Override
public void hasCSS(String name, Pattern pattern, HasCSSOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
hasCSS(name, expected, pattern, options);
}
private void hasCSS(String name, ExpectedTextValue expectedText, Object expectedValue, HasCSSOptions options) {
if (options == null) {
options = new HasCSSOptions();
}
FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
commonOptions.expressionArg = name;
String message = "Locator expected to have CSS property '" + name + "'";
if (expectedValue instanceof Pattern) {
message += " matching regex";
}
expectImpl("to.have.css", expectedText, expectedValue, message, commonOptions);
}
@Override
public void hasId(String id, HasIdOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = id;
expectImpl("to.have.id", expected, id, "Locator expected to have ID", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasId(Pattern pattern, HasIdOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.id", expected, pattern, "Locator expected to have ID matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasJSProperty(String name, Object value, HasJSPropertyOptions options) {
if (options == null) {
options = new HasJSPropertyOptions();
}
FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
commonOptions.expressionArg = name;
commonOptions.expectedValue = serializeArgument(value);
List<ExpectedTextValue> list = null;
expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions);
}
@Override
public void hasText(String text, HasTextOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expected.matchSubstring = false;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, text, "Locator expected to have text", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasText(Pattern pattern, HasTextOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
// Just match substring, same as containsText.
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, pattern, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasText(String[] strings, HasTextOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (String text : strings) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expected.matchSubstring = false;
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasText(Pattern[] patterns, HasTextOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (Pattern pattern : patterns) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasValue(String value, HasValueOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = value;
expectImpl("to.have.value", expected, value, "Locator expected to have value", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasValue(Pattern pattern, HasValueOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.value", expected, pattern, "Locator expected to have value matching regex", convertType(options, FrameExpectOptions.class));
}
@Override
public void isChecked(IsCheckedOptions options) {
expectTrue("to.be.checked", "Locator expected to be checked", convertType(options, FrameExpectOptions.class));
}
@Override
public void isDisabled(IsDisabledOptions options) {
expectTrue("to.be.disabled", "Locator expected to be disabled", convertType(options, FrameExpectOptions.class));
}
@Override
public void isEditable(IsEditableOptions options) {
expectTrue("to.be.editable", "Locator expected to be editable", convertType(options, FrameExpectOptions.class));
}
@Override
public void isEmpty(IsEmptyOptions options) {
expectTrue("to.be.empty", "Locator expected to be empty", convertType(options, FrameExpectOptions.class));
}
@Override
public void isEnabled(IsEnabledOptions options) {
expectTrue("to.be.enabled", "Locator expected to be enabled", convertType(options, FrameExpectOptions.class));
}
@Override
public void isFocused(IsFocusedOptions options) {
expectTrue("to.be.focused", "Locator expected to be focused", convertType(options, FrameExpectOptions.class));
}
@Override
public void isHidden(IsHiddenOptions options) {
expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class));
}
@Override
public void isVisible(IsVisibleOptions options) {
expectTrue("to.be.visible", "Locator expected to be visible", convertType(options, FrameExpectOptions.class));
}
private void expectTrue(String expression, String message, FrameExpectOptions options) {
List<ExpectedTextValue> expectedText = null;
expectImpl(expression, expectedText, null, message, options);
}
@Override
public LocatorAssertions not() {
return new LocatorAssertionsImpl(actualLocator, !isNot);
}
}

Some files were not shown because too many files have changed in this diff Show More